Рандомизация
Случайности - как работает рандом?
Каждый из нас сталкивается со «случайностями» в приложениях. Это может быть шанс выпадения добычи в видеоигре, случайное число в указанном диапазоне, случайный набор букв, цифр и символов для создания пароля. Но так ли всё случайно, или всё же это определённый алгоритм, который можно просчитать?
Случайностью называют концепцию, описывающую отсутствие закономерности, предсказуемости или детерминированной причины в наступлении событий. Это понятие есть в различных областях, и по-своему интерпретируется.
Научно и повседневно
Я не понаслышке знаю о случайности и вообще увлекался её возможностями - в детстве я создавал карту для Warcraft III, полностью построенную на рандомизации всего и вся - случайный герой, волны противников, предметы и бонусы. Это придаёт некое разнообразие и реиграбельность. А когда я залез в таблицы шансов дропа предметов, то рассчитывать долю вероятности при фарме предметов в Diablo или World Of Warcraft стало немного проще.
В дальнейшем, я регулярно сталкиваюсь с рандомизацией, и в жизни, и в работе, как и, наверное, любой другой человек. К примеру, можно создать приложение, которое будет подбирать случайный фильм на вечер (как здесь у Кинопоиска), или запускает случайный файл, выдаёт случайный ответ.
Но давайте начнём с науки - как вообще понимать эту случайность? Предлагаю сначала пробежаться поверхностно, а потом уже ссылаться.
В философии случайность рассматривается как противоположность необходимости. Аристотель выделял энтелехию (целенаправленность) и автоматон (случай). Случайные события не следуют из причинно-следственной цепи напрямую, но могут быть результатом пересечения независимых процессов. В классической механике господствовал детерминизм (предопределённость всех событий и действий) Лапласа, подразумевающий, что при полном знании начальных условий и законов природы, будущее можно предсказать полностью. Но в XX веке развивалась квантовая механика (поведение материи и энергии на уровне атомов и субатомных частиц) , и тезис предопределённости поставлен под сомнение - на микроуровне процессы, к примеру, распад радиоактивного атома, считаются принципиально случайными. Это и легло в основу вероятностной интерпретации реальности (Копенгагенская интерпретация). Что-то в стиле "случайности не случайны".
Альберт Эйнштейн тоже был сторонником упорядоченности и предсказуемости. Если подключить сюда религию, то и вовсе любые события можно оправдывать замыслом Создателя.
Инженерная же наука имеет довольно интересные примеры реализации концепции генерации случайностей, к примеру, фабрика случайности CURBy, которая работает, наверное, на самом глубоком уровне из рассматриваемых. Но то, что создают учёные, это больше напоминает на попытки «обуздать» саму суть генерации случайности, но на практике всё упирается в бизнес-цели. Зачем вам случайность? Криптография и моделирование. Всё остальное, можно сказать, обойдётся и «имитацией» случайности. А если дискутировать, подключая религию, то любое утверждение можно оправдать. Пример - истинные случайности, генерируемые космическим излучением, упираются в представление человека о космосе. Человек не покидал планету дальше, чем до Луны, и не знает природу таких излучений, которые вполне могут быть предопределенными какими-то высшими расами, силами, существами. Так что в науку сильно можно не углубляться - это чёрная дыра споров и рассуждений.
Математики, наверное, чаще всех работают с этой темой. Там случайность формализуется через теорию вероятностей и теорию случайных процессов. Событие считается случайным, если его исход нельзя точно предсказать, но можно охарактеризовать статистически - через вероятность, распределение, математическое ожидание и дисперсию. Например, бросок идеального шестигранного кубика - это дискретное равномерное распределение, где каждое из шести значений имеет вероятность 1/6. Логично. Современная аксиоматика теории вероятностей была разработана Колмогоровым и опирается на теорию меры. Существует также алгоритмическая теория случайности, в которой последовательность считается случайной, если она не может быть сжата алгоритмически - то есть, нет программы короче самой последовательности, которая могла бы её воспроизвести.
В повседневной жизни люди часто интерпретируют как случайные те события, которые кажутся им маловероятными или непредсказуемыми из-за недостатка информации. Психологические эффекты, такие как когнитивные искажения (например, ошибка игрока), приводят к ложным представлениям о случайности - человек склонен видеть закономерности в случайных данных (апофения) или ожидать выравнивания после серии одинаковых исходов.
Поэтому в науке случайность не абсолютная категория, скорее характеристика уровня предсказуемости системы с учётом доступных знаний и моделей - природа причинности, количественные модели неопределённости, восприятие и интерпретация событий.
А рандомизация?
Рандомизация — это процесс генерации последовательности значений, которые не подчиняются никакой детерминированной закономерности и распределены в соответствии с заданным вероятностным законом.
Цель рандомизации — имитировать непредсказуемость, необходимую в статистике, криптографии, моделировании, играх и других областях. Как раз пример с играми я и приводил выше.
Но, несмотря на всё наше восприятие и практическое применение рандомизации, эта генерация всё же алгоритмическая, и компьютер не создаёт «чистую» случайность.
Компьютеры по своей природе детерменированы (предопределены), ведь каждая операция выполняется строго по инструкциям, а состояние системы однозначно определяется предыдущим состоянием. Это делает невозможным получение истинно случайных чисел без внешнего источника энтропии. Поэтому большинство систем используют псевдослучайные числа — значения, генерируемые детерминированными алгоритмами, которые при правильной реализации статистически неотличимы от случайных для большинства практических задач.
Псевдослучайные числа генерируются с помощью генераторов псевдослучайных чисел (ГПСЧ) — алгоритмов, которые на основе начального значения (зерна, или seed) порождают последовательность чисел, проходящих статистические тесты на случайность.
Здесь можно выделить основные компоненты:
- Seed (зерно) — начальное значение, от которого начинается генерация. При одинаковом «зерне» последовательность будет полностью повторяться.
- Алгоритм — математическая функция, преобразующая текущее состояние ГПСЧ в следующее и выдающая число.
- Период — длина последовательности до её зацикливания.
Есть очень много алгоритмов, можете почитать дополнительно, если интересно (пример - линейный конгруэнтный метод). Но давайте не настолько глубоко в математику, вернёмся к информатике.
Существует такое понятие, как криптографическая стойкость. Для её обеспечения в задачах (например, генерация ключей), используются истинно случайные числа, получаемые из физических процессов - это шумы в электронных цепях, временные задержки между событиями (нажатия клавиш), квантовые эффекты в специализированных устройствах. Аппаратные генераторы случайных чисел измеряют хаотически изменяющиеся параметры протекающих физических процессов, что как раз и позволяет создавать криптографические ключи для зашифрованной передачи данных. Это реализовать сложно и дорого, поэтому существуют более простые варианты, вроде генератора псевдослучайных чисел, о котором шла речь выше.
Вы, наверное, замечали, если хоть раз создавали ключи для электронных подписей, когда система просила вас нажимать случайные клавиши и водить мышью - это именно то, что мы обсуждаем. Это случайность, которую программа не предусмотрит.
Так, получается, у нас есть истинная случайность, которая хаотична с точки зрения компьютера, и псевдослучайность, где последовательность всё равно подчиняется заданному распределению.
В Unix-подобных системах такие данные доступны через /dev/random и /dev/urandom, которые собирают энтропию из аппаратных и системных событий. В Windows — через CryptGenRandom или современный BCryptGenRandom.
На практике разработчик вызывает функцию вроде random() или rand(), которая:
- Инициализируется (явно или автоматически) seed’ом.
- Применяет внутренний алгоритм ГПСЧ.
- Возвращает число в заданном диапазоне, часто нормализованное к интервалу
[0, 1).
Рандомизация в компьютерах подразумевает симуляцию случайности через алгоритмы с высокой степенью непредсказуемости при известном seed. По сути, нам этого хватает для большинства приложений, и лишь только в случаях, когда надо от таких «предсказаний» защититься, мы используем уже источники истинной случайности.
Псевдослучайность скорее свойство последовательности значений - они выглядят как случайные с точки зрения статистических тестов (которыми и проверяют «случайность»), но на самом деле генерируются детерминированным алгоритмом. Эта последовательность полностью определяется начальным параметром - зерном (seed, тот самый). И при повторной инициализации тем же seed-ом, можно точно воспроизвести последовательность.
ГСПЧ применяется в информатике как раз для преобразования внутреннего состояния в выходное значение, обновляя состояние для следующего вызова. Можно привести три примера ГСПЧ:
Mersenne Twister (MT19937) — широко использовался в Python (random), R, MATLAB. Отличается огромным периодом и хорошим распределением, но не подходит для криптографии из-за предсказуемости по выходу.
xorshift, PCG (Permuted Congruential Generator) — современные алгоритмы с высокой скоростью и улучшенными статистическими свойствами. Blum Blum Shub — криптографически стойкий ГПСЧ, основанный на сложности факторизации больших чисел, но медленный.
Так что помним, что сейчас любая рандомизация на практике лишь компромисс между предсказуемостью машины и требованием непредсказуемости в приложениях, где воспроизводимость и производительность очень важны.
А в программировании?
Первые генераторы случайных чисел появились почти с самого начала, в 1940-1950-х годах, и были ненадёжными, к примеру, как Middle-square method, предложенный Джоном фон Нейманом. Позднее стали использовать линейные конгруэнтные генераторы (сокращённо LCG), ставшие основой для rand() в Си и аналогов в других языках.
Конечно, время повышало требования к качеству, периоду, безопасности псевдослучайных последовательностей. Сейчас библиотеки «круче», используют улучшенные алгоритмы, предоставляют разные уровни доступа, от простого рандома до криптографически безопасных генераторов.
Python
Модуль random в Python предоставляет интерфейс для генерации псевдослучайных чисел с использованием различных распределений. Seed инициализируется автоматически из источников энтропии, но может быть задан явно через random.seed(). При фиксированном seed последовательность полностью воспроизводится.
Для криптографии используют модуль secrets, основанный на системных источниках энтропии (getrandom() на Linux, CryptGenRandom на Windows).
import random
random.seed(42)
num = random.randint(1, 10)
print(f"Python: {num}")
f = random.random()
print(f"Float: {f:.4f}")
Здесь используется Mersenne Twister, но так было до 3.13 (теперь PCG64). Можете почитать об этом, если интересно.
Java
Класс java.util.Random — стандартный ГПСЧ в Java. Реализует линейный конгруэнтный метод (LCG) с дополнительным XOR-сдвигом. Seed может быть задан при создании экземпляра, и если не будет указан, то генерируется из System.nanoTime() и уникального счетчика.
import java.util.Random;
public class RandomJava {
public static void main(String[] args) {
Random rand = new Random(42);
int num = rand.nextInt(1, 11);
System.out.println("Java: " + num);
double f = rand.nextDouble();
System.out.printf("Float: %.4f%n", f);
}
}
Random использует LCG, а для криптостойкости - SecureRandom.
C# (.NET)
Класс System.Random в .NET — простой ГПСЧ, предназначенный для общих задач. Seed детерминирован при передаче числа, а без параметра определяется на основе системного времени.
using System;
class Program {
static void Main() {
var rand = new Random(42);
intnum = rand.Next(1, 11);
Console.WriteLine($"C#: {num}");
double f = rand.NextDouble();
Console.WriteLine($"Float: {f:F4}");
}
}
Такой вариант не является криптостойким (для безопасности есть RandomNumberGenerator).
JavaScript (Node.js)
Функция Math.random() возвращает число типа number (IEEE 754 double) в диапазоне [0, 1). Здесь нет способа инициализировать генератор, что делает невозможной воспроизводимость без внешних библиотек. Реализация зависит от движка.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
const num = getRandomInt(1, 11); // от 1 до 10
console.log(`JS: ${num}`);
const f = Math.random();
console.log(`Float: ${f.toFixed(4)}`);
Движки (V8, SpiderMonkey) используют свои ГПСЧ (например, xorshift128+). А для криптографии используется Web Crypto API.
Примерно такая картина в программировании. Все стандартные реализации ориентированы на общие задачи, где важна скорость и простота, но не требуется криптостойкость. Для воспроизводимости и контроля над последовательностью — особенно в JS — требуются сторонние решения.
А где применять такие случайности?
Конечно же, игры. Шанс критического удара, дропа предметов (например, 5% на легендарный лут), генерация карт, поведение NPC, событий, балансировка боевых механик. Думаю, знатоки MMORPG сразу поймут суть. Обычно это реализуется через генерацию случайного числа в заданном диапазоне и сравнение с порогом. Что-то вроде такого:
import random
def roll_drop(common_chance=0.7, rare_chance=0.2, legendary_chance=0.05):
roll = random.random()
if roll < legendary_chance:
return "Легендарный!"
elif roll < legendary_chance + rare_chance:
return "Редкий"
else:
return "Обычный"
print(roll_drop())
Создаётся таблица шансов (Loot Table).
Таблицы могут быть статическими (когда шансы фиксированные) или динамическими (с механиками накопления шанса при промахах).
В Dota 2 используется псевдослучайный механизм PRD (Pseudo-random distribution), чтобы критические удары не происходили сериями. Реальный шанс увеличивается после каждого промаха, пока событие не произойдёт.
В Diablo III шанс дропа зависит от уровня сложности, статов игрока и параметров предмета, что в огромной и сложной сумме обеспечивает широкую масштабируемость шансов, как отличный пример динамичности развития. Там используется статистический анализ, но есть и «капы» (лимиты) на шанс дропа легендарок, допустим после ~400 ящиков без легендарного предмета, шанс начинает расти, а на ~1000+ ящиках шанс достигает 100%. Четвертая часть использует похожую схему, а также некоторые активности гарантируют легендарность добычи.
В Diablo II каждый монстр имеет список возможных предметов с весами, и при убийстве генерируется случайное число. Предмет выбирается согласно весам (с учётом уровней лута mlvl, которые влияют на доступные типы предметов).
World of Warcraft использует систему персонального лута, где у каждого игрока свой ролл (roll) на лут, и при длительном отсутствии экипировки своего специализации (например, два месяца), шанс на подходящий предмет может быть повышен, допустим после нескольких попыток шанс на получение уникального вида предмета трансмога.
Некоторые игры подкручивают шанс, допустим, после определённого числа убийств босса легендарный предмет гарантированно выпадает.
Выделяют, к примеру, pity timer (от английского pity - жадность), игровой механизм, при котором вероятность получения редкого предмета постепенно увеличивается после каждого неудачного события, пока выпадение не произойдёт. Цель — уменьшить влияние «невезения» и обеспечить более предсказуемый опыт для игрока, особенно в системах с очень низкими шансами. Допустим, изначально шанс на редкий лут фиксирован (1%), при каждом промахе скрытый счётчик увеличивается, и когда достигает определённого порога, шанс становится 100%, гарантируя выпадение. Это контролируемая псевдослучайность, направленная на баланс между ощущением азарта и справедливостью.
Genshin Impact и Honkai: Star Rail явно используют pity system, где на персонажей 90-й пулл уже даёт 100%-й шанс на 5 звёзд, на оружие 80-й. И частично это всё сбрасывается при получении желаемого объекта. Genshin Impact как раз мотивирует игрока выполнять "молитвы".
Borderlands 2/3/4 акцентируют на редкость добычи, где используется процедурная генерация с весами. У легендарных врагов шанс на легендарный лут выше, а при повторных убийствах одного и того же босса шанс может временно снижаться (loot fatigue), чтобы избежать фарма. В Borderlands используется система добычи предметов, аналогичная Diablo, но с упрощениями.
В основном, по понятным причинам справедливости, разработчики игр не делятся подробностями о таблицах шансов или системе определения псевдослучайности добычи. А даже если мы узнаем правду - ничего не мешает им изменить систему ближайшим патчем.
Я упоминал ранее моделирование - это анализ, и методы вроде Монте-Карло для оценки интегралов, рисков, цен опционов, симуляции физических и прочих процессов. Это не частое явление, но всё же кейсы есть.
Криптография ставит высокие требования к надёжности, поэтому генерация ключей, солей требует истинно случайных или криптостойких псевдослучайных источников.
Алгоритмы в разной логике тоже рандомизируют. К примеру, QuickSort, или хэширование.
Интересные варианты применять рандом можно увидеть в тестировании, когда тестовые данные генерируются, или сценарии воспроизводятся через фиксированный seed.
Нейросети заслуживают не меньшего внимания. К примеру, в машинном обучении для инициализации весов нейросетей, разделения выборки, аугментации данных. Веса нейросети инициализируются случайными значениями (из нормального или равномерного распределения, например), это нужно для разрушения симметрии между нейронами, и обеспечения различной начальной точке в пространстве параметров.
Обучение также использует случайную выборку данных, что делает процесс стохастическим (когда невозможно точно предсказать дальнейшее состояние), и помогает избегать локальных минимумов, ускорять сходимость.
Генеративные модели (GAN, VAE, Diffusion) используют случайный вектор для генерации новых данных (изображений, текста), в них без случайности выдавался бы всегда детерминированный результат. Важно подчеркнуть, что для машинного обучения всё же важна воспроизводимость, в ML принято фиксировать seed'ы (numpy, torch, random), чтобы результаты экспериментов можно было повторить.
Как предвидеть или рассчитать случайность?
Если речь об истинной случайности - то никак, она непредсказуема.
Но когда контекстом являются псевдослучайные последовательности и статистические модели, то частичная предсказуемость там есть, если информации достаточно.
- Воспроизвести псевдослучайную последовательность.
Если известен алгоритм генератора (тот же упомянутый Mersenne Twister) и начальное зерно, то вся последовательность может быть точно воспроизведена, и именно это используют в научных симуляциях и тестировании. В том же Python, при каждом запуске с seed(123) результат будет одинаковым.
- Статистика и прогнозирование.
Думаю, вы уже понимаете, что это «не точно», и отдельные исходы непредсказуемы, но всё же распределение подчиняется законам теории вероятностей. Например, при броске кубика шанс выпадения «6» - 1/6, а после 6000 бросков ожидается около 1000 шестёрок (закон больших чисел), но внимание на «ожидается», то есть, гарантию никто не даст. Всё же, статистика позволяет рассчитывать ожидаемые значения, дисперсию и доверительные интервалы.
Но не конкретный следующий результат, хехе.
- Атаки на слабые ГСПЧ. Буквально - речь о взломе, ведь некоторые генераторы (например, LCG) можно взломать по нескольким выходным значениям. Зная часть последовательности, можно восстановить параметры и предсказать будущие числа.
Но в общем, случайность нельзя предсказать точно, можно лишь моделировать статистически и воспроизводить.
Что же, получается, большая часть "случайности" в цифровом мире, представляет лишь "подкрученные значения"? Получается, что так.