Як слід перевірити випадковість?


127

Розглянемо метод випадкового переміщення елементів у масиві. Як би ви написали простий, але надійний блок-тест, щоб переконатися, що це працює?

Я придумав дві ідеї, обидві з яких мають помітні вади:

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

Розглянемо другу функцію, яка імітує рулони кісток і повертає випадкове число. Як би ви протестували цю функцію? Як би ви перевірили цю функцію ...

  • ніколи не повертає число за межами заданих меж?
  • повертає числа у дійсному розподілі? (Уніфікована для одного штампу, нормальна для великої кількості кісток.)

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


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


35
Щільна муфта показує голову. Передайте в об’єкт, який генерує випадкові числа. Потім під час тестування ви можете передати об'єкт, який генерує заданий набір чисел, для яких ви знаєте, як виглядає колода після перетасування. Ви можете перевірити випадковість генератора випадкових чисел окремо.
Мартін Йорк

1
Я б настійно розглядав можливість використання існуючої бібліотечної програми для переміщення (java Collections.shuffle () або подібного). На веб-сайті developer.com/tech/article.php/616221/… можна прочитати застережливу розповідь про написання алгоритму помилкових перетасовок. Для написання функції d6 () слід перевірити її достатньо, щоб бути впевненою, що вона не генерує число поза діапазоном, а потім зробить тест на квадратичне чи на розподіл (чі-квадрат є досить чутливим до псевдо випадкових послідовностей). Подивіться також на коефіцієнт послідовного співвідношення.

"Це покладається на випадкову функцію, яка завжди повертає однакові значення, задані тим самим насінням. Однак, це іноді є невірним припущенням." Я перейшов за посиланням, і не бачу недійсного припущення. Це чітко сказано: "Якщо одне і те ж насіння використовується неодноразово, генерується однаковий ряд чисел".
Kyralessa

@Kyralessa "Впровадження генератора випадкових чисел у класі Random не гарантовано залишатиметься однаковим у основних версіях .NET Framework." Тож не величезне занепокоєння, але все-таки щось врахувати.
dlras2

4
@Kyralessa Я пропустив важливу половину цієї цитати: "Як результат, ваш додаток не повинен припускати, що одне насіння призведе до тієї ж псевдовипадкової послідовності в різних версіях .NET Framework."
dlras2

Відповіді:


102

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

На щастя, існує чимало статистичних тестів, які можна запустити, наприклад, батарея Diehard тестів випадковості . Дивитися також:

  1. Як поділити тест на псевдогенератор випадкових чисел?

    • Стів Джессоп рекомендує знайти перевірену реалізацію того ж алгоритму RNG, який ви використовуєте, і порівняти його вихід з вибраними насінням проти вашої власної реалізації.
    • Грег Х'югілл рекомендує ЛОР- набір статистичних тестів.
    • Джон Д. Кук звертається до читачів зі своєю статтею CodeProject Просте покоління випадкових чисел , що включає реалізацію тесту Колмогорова-Смірнова, згаданого в томі Дональда Кнута 2, «Напівлінійні алгоритми».
    • Кілька людей рекомендують перевірити, щоб розподіл отриманих чисел було рівномірним, тест Chi-квадрата та перевіряли, чи є середнє та стандартне відхилення в межах очікуваного діапазону. (Зауважимо, що тестування самого розподілу недостатньо. [1,2,3,4,5,6,7,8] - це рівномірний розподіл, але це, звичайно, не випадково.)
  2. Тестування блоку за допомогою функцій, що повертають випадкові результати

    • Брайан Генісіо вказує, що глузування з RNG є одним із варіантів для повторення тестів, і надає код зразка C #.
    • Знову ще кілька людей вказують на використання фіксованих значень насіння для повторюваності та прості тести для рівномірного розподілу, Chi-квадрат тощо.
  3. Експериментальне тестування випадковості - це стаття у вікі, яка розповідає про багато проблем, які вже торкнулися, намагаючись перевірити те, що за своєю природою не повторюється. Один цікавий біт, який я зібрав з нього, був такий:

    Я раніше бачив winzip як інструмент для вимірювання випадковості файлу значень раніше (очевидно, чим менше він може стискати файл, тим менше він випадковий).


Ще один хороший набір тестів для статистичної випадковості - це "ent", знайдений у fourmilab.ch/random .

1
Чи можете ви підсумувати деякі посилання, які ви опублікували, для повноти відповіді?
dlras2

@DanRasmussen Звичайно, я встигну це зробити протягом вихідних.
Білл Ящірка

4
“Проблема з… випадковістю полягає в тому, що немає очікуваного значення…” - як іронічно, враховуючи, що “очікуване значення” є чітко визначеним терміном у статистиці. І хоча це не те, що ви мали на увазі, воно натякає на правильне рішення: за допомогою відомих властивостей статистичних розподілів у поєднанні з випадковим вибіркою та статистичними тестами визначити, чи працює алгоритм з дуже високою ймовірністю. Так, це не класичний одиничний тест, але я хотів згадати його, оскільки в найпростішому випадку він просто дивиться на розподіл… очікуваного значення .
Конрад Рудольф

2
Існує оновлена ​​версія знаменитого Diehard Battery of Tests of Randomness at Dieharder, що включає Статистичний тестовий набір (STS), розроблений Національним інститутом стандартів і технологій (NIST). Він доступний для запуску в Ubuntu та, імовірно, інших дистрибутивах: phy.duke.edu/~rgb/General/dieharder.php
nealmcb

21

1. Тестування алгоритму

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

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2. Подивіться, чи має ваша випадкова функція сенс

До одиничного тестування слід додати тест, який виконується кілька разів, і стверджує, що результати

  • знаходяться в межах встановлених вами меж (отже, рулон з кістки - від 1 до 6) та
  • покажіть розсудливий розподіл (зробіть декілька пробних тестів і подивіться, чи розподіл знаходиться в межах х% від того, що ви очікували, наприклад, для рулону з кістки ви повинні побачити, що 2піднімається між 10% і 20% (1/6 = 16,67%) час, враховуючи, що ви прокатали його 1000 разів).

3. Інтеграційний тест алгоритму та випадкової функції

Як часто ви очікуєте, що ваш масив буде відсортований за оригінальним сортуванням? Відсортуйте пару сотень разів і стверджуйте, що лише x% часу сортування не змінюється.

Це насправді вже інтеграційний тест, ви випробовуєте алгоритм разом із випадковою функцією. Коли ви використовуєте реальну випадкову функцію, ви більше не зможете піти з одного тестового запуску.

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


14

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

Правильний тест для PRNG передбачає запустити його принаймні 100 разів, а потім перевірити розподіл результатів (що є прямою відповіддю на другу частину питання).

Відповідь на перше запитання майже однакова: проведіть тест приблизно 100 разів за допомогою {1, 2, ..., n} і підрахуйте кількість разів, коли кожен елемент був у кожній позиції. Всі вони повинні бути приблизно рівними, якщо метод перетасовки є корисним.

Зовсім інша справа - тестування PRNG-класів, що мають криптовалюту. Це питання, в якому ви, мабуть, не повинні зупинятися, якщо ви справді не знаєте, що робите. Люди, як відомо, руйнують (читайте: відкриті катастрофічні діри) хороші криптосистеми лише за допомогою декількох «оптимізацій» або тривіальних редагувань.

EDIT: Я ретельно перечитав питання, головну відповідь і свою власну. Хоча моменти, які я заробляю, досі стоять, я би другий відповів Білла Ящірка. Одиничні тести є булевими за своєю суттю - вони або провалюються, або досягають успіху, і тому не підходять для тестування "наскільки хорошими" є властивості PRNG (або методу, що використовує PRNG), оскільки будь-яка відповідь на це питання була б кількісною. , а не полярний.


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

@octern Дякую, я не знаю, як я міг би написати, що ... до цього часу було абсолютно неправильно ...
K.Steff

6

Для цього є дві частини: тестування рандомізації та тестування речей, які використовують рандомізацію.

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

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


6

Нехай він працює багато разів і візуалізує ваші дані .

Ось приклад переміщення з кодування Horror , ви можете бачити, що алгоритм у порядку чи ні:

введіть тут опис зображення

Неважко помітити, що кожен можливий елемент повертається принаймні один раз (межі в порядку) і що розподіл в порядку.


1
+1 візуалізація є ключовим. Мені завжди подобався приклад із зображенням пінгвіна в розділі ЄЦБ статті « Блок-шифр» ). Автоматизоване програмне забезпечення рідко може виявити подібні закономірності
Maksee

Так? Сенс цієї візуалізації полягає в тому, щоб показати, що розподіл не в порядку. Алгоритм наївного переміщення робить певні замовлення набагато більш імовірними, ніж інші. Зауважте, наскільки далеко праворуч простягаються смуги 2341, 2314, 2143 та 1342?
hvd

4

Загальні вказівники, які я вважаю корисними для роботи з кодом, який приймає рандомізований ввід: Перевірте крайові випадки очікуваної випадковості (значення max і min, а також значення max + 1 та min-1, якщо це застосовується). Перевірте місця (на, вище та нижче), де числа мають точки перегину (тобто -1, 0, 1 або більше 1, менші за 1 та негативні для випадків, коли дробове значення може зіпсувати функцію). Перевірте кілька місць повністю поза дозволеним входом. Перевірте кілька типових випадків. Ви також можете додати випадкове введення, але для одиничного тесту, який має небажаний побічний ефект, те, що те саме значення не перевіряється кожного разу, коли тест запускається (насіннєвий підхід може працювати, хоча протестуйте перші 1000 випадкових чисел із насіння S або дещо).

Для тестування виходу випадкової функції важливо визначити мету. Що стосується карт, чи є мета перевірити рівномірність генератора 0-1 випадкових випадків, щоб визначити, чи всі 52 карти відображаються в результаті, чи якась інша мета (можливо, весь цей список і більше)?

У конкретному прикладі ви повинні припустити, що ваш генератор випадкових чисел непрозорий (так само, як не має сенсу перевіряти системний виклик або malloc, якщо ви не пишете ОС). Можливо, буде корисно виміряти генератор випадкових чисел, але ваша мета - не записувати генератор випадкових чисел, а лише бачити, що ви отримуєте 52 картки кожного разу і що вони змінюють порядок.

Це довгий шлях, щоб сказати, що тут дійсно два тестові завдання: перевірка того, що RNG виробляє правильний розподіл, і перевірка того, що код перетасовки вашої картки використовує цей RNG для отримання рандомізованих результатів. Якщо ви пишете RNG, використовуйте статистичний аналіз, щоб довести свій розподіл, якщо ви пишете перетасовувач карт, переконайтеся, що в кожному виході є 52 неодноразові карти (це кращий випадок для перевірки шляхом перевірки, яку ви використовуєте РНГ).


4

Ви можете розраховувати на безпечні генератори випадкових чисел

У мене просто була жахлива думка: ти не пишеш власного генератора випадкових чисел?

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

Тестування вашого коду

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

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

Тестування безпечного генератора випадкових чисел

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


1

Ну, ви ніколи не будете впевнені на 100%, тому найкраще, що ви можете зробити, це те, що цілком ймовірно, що числа є випадковими. Оберіть ймовірність - скажіть, що вибірка цифр чи предметів вийде x разів, отримавши мільйон вибірок, в межах помилки. Запустіть річ мільйон разів і подивіться, чи не в межах поля. На щастя, комп'ютери полегшують подібні речі.


Але чи вважаються одиничні тести такою доброю практикою? Я завжди думав, що одиничний тест повинен бути максимально простим: ніяких циклів, гілок чи нічого іншого, чого можна уникнути.
dlras2

4
Одиничні тести повинні бути правильними . Якщо потрібно розгалуження, петлі, рекурсія - ось ціна. Ви не можете перевірити надзвичайно складні, високооптимізовані класи з однолінійними тестами. Я реалізував алгоритм Dijkstra для тестування класу один раз.
К.Стефф

3
@ K.Steff, вау. Ви перевіряли тест свого блоку, щоб переконатися, що алгоритм Dijkstra був правильним?
Вінстон Еверт

Хороший момент, по суті - так, але на цей раз з "банальними" тестами. Однак вони також були одиничними тестами для оригінальної програми (A *). Я думаю, що це дійсно хороша практика - тестування швидких алгоритмів знову сприймає кульгаві (але правильні) реалізації.
К.Стефф

1

Для того, щоб перевірити , що джерело випадкових чисел генерує то , що по крайней мере , має вигляд випадковості, я б тест генерувати досить велику сукупність електронних даних, записати їх у тимчасовий файл, а потім розщедрюватися Fourmilab в Лорі інструмент. Дайте ent перемикач -t (terse), щоб він створив легкий для розбору CSV. Потім перевірте різні цифри, щоб побачити, що вони "хороші".

Щоб визначити, які цифри хороші, використовуйте відоме джерело випадковості для калібрування вашого тесту. Тест майже завжди повинен проходити, коли дається хороший набір випадкових чисел. Оскільки навіть по-справжньому випадкова послідовність має ймовірність генерувати послідовність, яка, як видається, є невипадковою, ви не можете отримати тест, який обов'язково пройде. Ви просто вибираєте порогові значення, які роблять малоймовірним, що випадкова послідовність призведе до невдачі тесту. Чи не випадковість весела?

Примітка. Ви не можете написати тест, який показує, що PRNG генерує "випадкову" послідовність. Можна написати лише тест, який, якщо він пройде, вказує на певну ймовірність того, що послідовність, породжена PRNG, є "випадковою". Ласкаво просимо в радість випадковості!


1

Випадок 1: Тестування переміщення:

Розгляньте масив [0, 1, 2, 3, 4, 5], перемішайте його, що може піти не так? Звичайні речі: а) взагалі немає перемішування, б) перемішування 1-5, але не 0, переміщення 0-4, але не 5, переміщення та завжди генерування одного і того ж шаблону, ...

Один тест, щоб зловити їх усіх:

Перемішайте 100 разів, додайте значення у кожен слот. Сума кожного слота повинна бути схожою між собою. Avg / Stddev можна розрахувати. (5 + 0) /2=2,5, 100 * 2,5 = 25. Очікуване значення, наприклад, близько 25.

Якщо значення знаходяться поза діапазоном, є невеликий шанс, що ви отримали помилковий негатив. Ви можете порахувати, наскільки великий такий шанс. Повторіть тест. Ну - звичайно, є невеликий шанс, що тест пройде 2 рази поспіль. Але у вас немає рутини, яка автоматично видаляє джерело, якщо тест не вдався, чи не так? Запустіть його знову!

Він може вийти з ладу 3 рази поспіль? Можливо, вам варто спробувати удачу на лотереї.

Випадок 2: Згорніть кістки

Питання з рубанної кістки - це те саме питання. Киньте кістки 6000 разів.

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.