Що не так з цим "наївним" алгоритмом переміщення?


23

Це подальше запитання щодо Stackoverflow щодо випадкового переміщення масиву .

Існують встановлені алгоритми (такі як Knuth-Fisher-Yates Shuffle ), які слід використовувати для переміщення масиву, а не покладатися на "наївні" спеціальні реалізації.

Мені зараз цікаво довести (або спростувати), що мій наївний алгоритм порушений (як у: не створює всіх можливих перестановок з однаковою ймовірністю).

Ось алгоритм:

Кілька разів цикліть (довжина масиву має робити), і в кожній ітерації отримайте два індекси випадкових масивів і поміняйте там два елементи.

Очевидно, що для цього потрібно більше випадкових чисел, ніж KFY (вдвічі більше), але окрім того, чи працює він належним чином? І якою була б відповідна кількість ітерацій (достатня "довжина масиву")?


4
Я просто не можу зрозуміти, чому люди думають, що цей замінник "простіший" або "наївніший", ніж FY ... Коли я вирішував цю проблему вперше, я щойно реалізував FY (не знаючи, що вона навіть має ім'я) , тільки тому, що це здалося для мене найпростішим способом.

1
@mbq: особисто я вважаю їх однаково простими, хоча я згоден, що FY здається мені більш "природним".
nico

3
Коли я досліджував алгоритми перетасовки після написання власного (я відтоді відмовився від практики), у мене все було "святе лайно, це вже зроблено, і воно має назву !!"
JM не є статистиком

Відповіді:


12

Він порушений, хоча якщо ви виконаєте достатню кількість перетасовок, це може бути відмінним наближенням (як показали попередні відповіді).

Просто, щоб зрозуміти, що відбувається, подумайте, як часто ваш алгоритм генерує перетасування масиву елементів, у якому зафіксовано перший елемент, . Коли перестановки генеруються з однаковою ймовірністю, це має відбуватися через часу. Нехай є відносною частотою цього явища після перетасовок з вашим алгоритмом. Давайте також будемо щедрі, і припустимо, що ви фактично вибираєте окремі пари індексів рівномірно для своїх перетасовок, так що кожна пара вибирається з вірогідністю =k 2 1 / k p n n 1 / ( kкк21/кpнн1/(к2)2/(к(к-1)). (Це означає, що "тривіальних" перетасовок немає даремно. З іншого боку, він повністю порушує ваш алгоритм для двоелементного масиву, тому що ви чергуєте між фіксацією двох елементів і заміною їх, тому якщо ви зупинитесь після заздалегідь заданої кількості кроки, випадковості до результату немає!)

Ця частота задовольняє просту повторюваність, тому що перший елемент виявляється у своєму первісному місці після перетасовок двома роз'єднаними способами. Одне полягає в тому, що воно було зафіксовано після переміщення, а наступне переміщення не переміщує перший елемент. Інша полягає в тому, що вона була переміщена після переміщення, але переміщення переміщує її назад. Шанс не переміщення першого елемента дорівнює = , тоді як шанс переміщення першого елемента назад дорівнює = . Звідки:n nн+1нн( k - 1н+1ст (k-2)/k1/ ( k(к-12)/(к2)(к-2)/к 2/(k(k-1))1/(к2)2/(к(к-1))

p0=1
тому що перший елемент починається на належному місці;

pн+1=к-2кpн+2к(к-1)(1-pн).

Рішення є

pн=1/к+(к-3к-1)нк-1к.

Віднімаючи , ми бачимо, що частота неправильна . Для великих і хорошим наближенням є . Це показує, що похибка в цій конкретній частоті експоненціально зменшиться з кількістю свопів відносно розміру масиву ( ), що вказує, що з великими масивами буде важко виявити, якщо ви зробили відносно велику кількість свопів - але помилка завжди є.( k - 31/к knk-1(к-3к-1)нк-1ккнп/кk1kexp(2nk1)n/k

Важко забезпечити всебічний аналіз помилок на всіх частотах. Імовірно, вони будуть поводитись так, як цей, що свідчить про те, що як мінімум вам знадобиться (кількість свопів), щоб бути достатньо великим, щоб помилка була прийнятно малою. Наближене рішенняn

n>12(1(k1)log(ϵ))

де має бути дуже малим порівняно з . Звідси випливає, що повинно бути в кілька разів для рівномірних наближених ( тобто , де знаходиться в порядку рази або близько.)1 / k n k ϵ 0,01 1 / kϵ1/knkϵ0.011/k

Все це ставить питання: чому ви вирішили використовувати алгоритм, який не зовсім (але лише приблизно) правильний, використовує точно ті ж методи, що й інший алгоритм, який, очевидно, правильний, але який вимагає більше обчислень?

Редагувати

Коментар Тіло доречний (і я сподівався, що ніхто цього не зазначить, тому я міг би пощадити цю додаткову роботу!). Дозвольте пояснити логіку.

  • Якщо ви впевнені, що кожен раз генеруєте фактичні замінники, ви повністю закручені. Проблема, яку я вказав для випадку поширюється на всі масиви. Лише половину всіх можливих перестановок можна отримати, застосувавши парну кількість свопів; інша половина виходить шляхом застосування непарної кількості свопів. Таким чином, у цій ситуації ви ніколи не можете генерувати ніде поблизу рівномірного розподілу перестановок (але існує стільки можливих, що симуляційне дослідження для будь-якого значного не зможе виявити проблему). Це справді погано.kk=2к

  • Тому розумно генерувати свопи навмання, генеруючи дві позиції незалежно навмання. Це означає, що є шанс кожного разу заміняти елемент собою; тобто нічого не робити. Цей процес трохи сповільнює алгоритм: після кроків ми очікуємо, що відбудеться лише приблизно справжніх замінів.n k - 11/knk1kN<N

  • Зауважте, що розмір помилки монотонно зменшується з кількістю чітких свопів. Тому проведення менших свопів в середньому також збільшує помилку, в середньому. Але це ціна, яку ви повинні бути готові заплатити, щоб подолати проблему, описану в першій кулі. Отже, моя оцінка помилок є консервативно низькою, приблизно в коефіцієнті .(k1)/k

Я також хотів зазначити цікавий очевидний виняток: уважний погляд на формулу помилки говорить про те, що у випадку помилки немає . Це не помилка: це правильно. Однак тут я розглянув лише одну статистику, що стосується рівномірного розподілу перестановок. Той факт, що алгоритм може відтворити цю статистику, коли (а саме отримання потрібної частоти перестановок, що фіксують будь-яку задану позицію), не гарантує, що перестановки дійсно розподіляються рівномірно. Дійсно, після фактичних свопів, єдиними можливими перестановками, які можна генерувати, є ,k = 3 2 n ( 123 ) ( 321 ) 2 n + 1 ( 12 ) ( 23 ) ( 13 )k=3k=32n(123)(321)та особи. Тільки остання фіксує будь-яку задану позицію, тому дійсно рівно третина перестановок фіксує позицію. Але половина перестановок відсутня! В іншому випадку, після фактичних свопів, єдиними можливими перестановками є , та . Знову-таки, саме одна з них виправить будь-яку задану позицію, тому ми знову отримаємо правильну частоту перестановок, що фіксують це положення, але знову-таки отримаємо лише половину можливих перестановок.2n+1(12)(23)(13)

Цей невеликий приклад допомагає розкрити основні напрямки аргументу: будучи «щедрими», ми консервативно недооцінюємо показник помилок для однієї конкретної статистики. Оскільки цей показник помилок не є нульовим для всіх , ми бачимо, що алгоритм порушений. Крім того, аналізуючи занепад швидкості помилок для цієї статистики, ми встановлюємо нижню межу щодо кількості ітерацій алгоритму, необхідних для сподівання на рівномірний розподіл перестановок.k4


1
"Давайте також будемо щедрі, і припустимо, що ви насправді вибираєте окремі пари індексів рівномірно для своїх переміщень". Я не розумію, чому таке припущення можна зробити і наскільки воно щедре. Здається, відкинути можливі перестановки, що призводить до ще менш випадкового розподілу.
Тило

1
@Thilo: Дякую Ваш коментар заслуговує на розширену відповідь, тому я помістив його у самій відповіді. Дозвольте зазначити тут, що «щедрий» насправді не відкидає будь-яких перестановок: він просто виключає кроки в алгоритмі, які в іншому випадку нічого б не зробили.
whuber

2
Цю проблему можна повністю проаналізувати як ланцюг Маркова на графіку Кейлі пермутаційної групи. Числові обчислення для k = 1 до 7 (матриця 5040 на 5040!) Підтверджують, що найбільші власні значення за розміром (після 1 і -1) є саме . Це означає, що після того, як ви впоралися з проблемою чергування знака перестановки (що відповідає власній величині -1), помилки в усіх ймовірностях розпадаються зі швидкістю або швидше. Я підозрюю, що це продовжує застосовуватися для всіх великих . ( 1 - 2 / ( k - 1 ) ) n k(k3)/(k1)=12/(k1)(12/(k1))nk
whuber

1
Ви можете зробити набагато краще, ніж оскільки ймовірності інваріантні для класів кон'югації, і є лише розділів із тож ви можете замість цього проаналізувати матрицю. 15 7 15 × 155040×504015715×15
Дуглас Заре

8

Я думаю, що ваш простий алгоритм перемістить картки правильно, оскільки кількість перетасовок має тенденцію до нескінченності.

Припустимо, у вас є три карти: {A, B, C}. Припустимо, що ваші картки починаються в такому порядку: A, B, C. Потім після одного перетасування ви маєте такі комбінації:

{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}

Отже, вірогідність того, що карта А опиниться в положенні {1,2,3}, становить {5/9, 2/9, 2/9}.

Якщо ми перетасуємо картки вдруге, то:

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) 

Це дає 0,407.

Використовуючи ту саму ідею, ми можемо сформувати відношення рецидиву, тобто:

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).

Кодування цього в R (див. Код нижче) дає ймовірність того, що карта A опиниться в положенні {1,2,3} як {0,33334, 0,33333, 0,33333} після десяти переміщень.

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

1
+1. Це свідчить про те, що ймовірність того, що дана карта опиниться в заданій позиції, наближається до очікуваного співвідношення в міру збільшення кількості перетасовок. Однак те ж саме було б і з алгоритмом, який просто обертає масив один раз випадковою сумою: Усі картки мають однакову ймовірність потрапити у всі позиції, але випадковості все ще немає (масив залишається відсортованим).
Тило

@Thilo: Вибачте, я не слідкую за вашим коментарем. "Алгоритм обертається випадковою кількістю", але все ще "немає випадковості"? Чи можете ви пояснити далі?
csgillespie

Якщо ви "перемішуєте" N-елементний масив, обертаючи його між 0 і N-1 позиціями (випадковим чином), то кожна карта має однакову ймовірність опинитися в будь-якій з N позицій, але 2 все ще завжди знаходиться між 1 та 3.
Тило

1
@Thio: Ах, я розумію. Ну ви можете розробити ймовірність (використовуючи абсолютно таку ж ідею, що і вище), для Pr (A в позиції 2) і Pr (A в позиції 3) - дито для карт B і C. Ви побачите, що всі ймовірності мають тенденцію до 1/3. Примітка: моя відповідь дає лише конкретний випадок, тоді як @whuber приємна відповідь дає загальний випадок.
csgillespie

4

Один із способів побачити, що ви не отримаєте ідеально рівномірного розподілу, - це поділ. При рівномірному розподілі ймовірність кожної перестановки становить. При генерації послідовності випадкових перестановок, а потім збирати послідовності від їхнього продукту, ймовірності ви отримаєте мають вигляд для деякого цілого числа . Якщо , тоді . За постулатом Бертранда (теорема), для існують прайми, які зустрічаються в знаменнику і не поділяють , томуне є цілим числом, і немає способу розподілити транспозиції рівномірно на1/n!tA/n2tA1/n!=A/n2tн2т/н!=Ан3нн2т/н!н!перестановки. Наприклад, якщо , то знаменникділиться на тоді як знаменника немає, тому не може зменшитись до.н=521/52!3,5,7,...,471/522тА/522т1/52!

Скільки потрібно, щоб наблизити довільну перестановку? Генерація випадкової перестановки випадковими пересуваннями була проаналізована Діаконісом і Шахшахані, використовуючи теорію представлення симетричної групи в

Diaconis, P., Shahshahani, M. (1981): "Генерування випадкової перестановки випадковими транспозиціями". З. Варш. Verw. Геб. 57, 159–179.

Один з висновків полягав у тому, що потрібно переходити в тому сенсі, що після перестановки далеко не випадкові, а після результат близький до випадкових як у розумінні загальної зміни, так і відстані. Такий тип явища обрізання є поширеним у випадкових прогулянках по групах і пов'язаний з відомим результатом того, що вам потрібно стрілецьких переміщень, перш ніж колода стане близькою до випадкової.12нжурналн(1-ϵ)12нжурналн(1+ϵ)12нжурналнL27


2

Майте на увазі, що я не статистик, але я поставлю свої 2 центи.

Я зробив невеликий тест на R (обережно, це дуже повільно для високих numTrials, код, ймовірно, може бути оптимізований):

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)
    }

Це створить матрицю swapsз numTrials+1рядками (по одній пробі + оригінал) та numElementsстовпцями (по одному на кожен векторний елемент). Якщо метод є правильним, розподіл кожного стовпця (тобто значень для кожного елемента протягом випробувань) не повинен відрізнятися від розподілу вихідних даних.

Оскільки наші вихідні дані зазвичай розподілялися, ми очікували, що всі стовпці не будуть відхилятися від цього.

Якщо ми біжимо

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")

Ми отримуємо:

Гістограми випадкових випробувань

що виглядає дуже перспективно. Тепер, якщо ми хочемо статистично підтвердити, що розподіли не відхиляються від початкових, я думаю, що ми могли б використати тест Колмогорова-Смірнова (будь-ласка, може якийсь статистик підтвердити, що це правильно?) Та зробимо, наприклад,

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

Що дає нам p = 0,9926

Якщо ми перевіримо всі стовпці:

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

І ми біжимо

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

ми отримуємо:

Гістограма тесту Колмогорова-Смірнова р р

Отже, для переважної більшості елементів масиву ваш метод своп дав хороший результат, оскільки ви також можете переглядати квартилі.

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

Зауважте, що, очевидно, із меншою кількістю випробувань ситуація не така хороша:

50 випробувань

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

100 випробувань

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

500 випробувань

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

0

Ось як я інтерпретую ваш алгоритм у псевдокоді:

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]

2×ленгтгод×нум_pассес[0,ленгтгод-1]ленгтгод

ленгтгод2×ленгтгод×нум_pассес

ленгтгод!ленгтгод!<ленгтгод2×ленгтгод×нум_pассес

ленгтгод!|ленгтгод2×ленгтгод×нум_pассес

pp<ленгтгодpленгтгодленгтгод>2p|ленгтгод! l e n g t h ! l e n g t h 2 × l e n g t h × n u m _ p a s s e sленгтгод2×ленгтгод×нум_pассесленгтгод!ленгтгод2×ленгтгод×нум_pассесленгтгод>2

ленгтгодp<ленгтгодленгтгод-1ленгтгод-1ленгтгод

ленгтгодленгтгод-1ленгтгод!ленгтгод!|ленгтгод!. Не важко показати, що кожен слід призводить до різної перестановки, і звідси легко помітити, що Фішер-Йейтс генерує кожну перестановку з однаковою ймовірністю.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.