Метод обратной функции

У нас есть равномерные числа из $[0, 1)$ — и есть потребность в выборках из совсем других распределений: экспоненциального для времени между отказами, нормального для шумов измерений, какого угодно. Как перекинуть мост от ровного ковра равномерности к нужной форме? Самый изящный и универсальный способ — метод обратной функции.

Метод обратной функции (inverse transform sampling) — приём генерации случайной величины с заданным распределением: берут равномерное число $u$ из $[0, 1)$ и подставляют его в обратную функцию распределения, получая выборку нужной формы.

В основе метода лежит одно ключевое понятие — функция распределения. Напомним: CDF (cumulative distribution function), обозначаемая $F(x)$, — это вероятность того, что случайная величина окажется не больше $x$. Она всегда монотонно растёт от 0 (слева) до 1 (справа). И вот ключевое наблюдение: значения самой CDF равномерно распределены на $[0, 1)$. Значит, если «прочитать формулу наоборот» — взять равномерное $u$ и найти такой $x$, что $F(x) = u$, — мы получим величину как раз с нужным распределением.

Рецепт из трёх шагов

Метод сводится к универсальному алгоритму, который работает для любого распределения с обратимой CDF:

  1. Взять CDF целевого распределения $F(x)$ — формулу, описывающую накопленную вероятность.
  2. Приравнять её к равномерному числу: $F(x) = u$, где u взято из random.random().
  3. Решить уравнение относительно x: выразить $x = F^{-1}(u)$. Полученное $x$ и есть выборка из нужного распределения.

Геометрически это «отражение через диагональ»: мы переходим от вопроса «какова вероятность не превысить x?» к обратному «какому x соответствует данная накопленная вероятность u?». Поскольку u бежит равномерно по $[0, 1)$, а CDF в густонаселённых областях распределения растёт круто, туда же чаще и «приземляются» наши x — так и воспроизводится нужная плотность.

Разбор на экспоненциальном распределении

Экспоненциальное распределение — классика моделирования: им описывают время до отказа прибора, интервалы между приходами клиентов, длительность телефонного разговора. У него один параметр $\lambda$ (лямбда) — интенсивность событий. Его CDF имеет вид $F(x) = 1 - e^{-\lambda x}$ для $x \ge 0$.

Применим рецепт. Приравниваем к равномерному числу и решаем относительно x:

$$\begin{aligned} F(x) &= u \\ 1 - e^{-\lambda x} &= u \\ e^{-\lambda x} &= 1 - u \\ -\lambda x &= \ln(1 - u) \\ x &= -\ln(1 - u) / \lambda \end{aligned}$$

Готова формула генерации: $x = -\ln(1 - u) / \lambda$. Каждое равномерное u мы прогоняем через неё и получаем число, распределённое экспоненциально. Проверим это эмпирически: сгенерируем десять тысяч выборок при $\lambda = 0.5$ и сравним среднее с теоретическим. Для экспоненциального распределения математическое ожидание равно $1/\lambda$, то есть при $\lambda = 0.5$ должно получиться 2.0:

import random, math, statistics
random.seed(3)
lam = 0.5
samples = [-math.log(1 - random.random()) / lam for _ in range(10000)]
print(f"Сгенерировано выборок: {len(samples)}")
print(f"Среднее выборки: {statistics.mean(samples):.4f}")
print(f"Теоретическое 1/lambda: {1 / lam}")

Вывод:

Сгенерировано выборок: 10000
Среднее выборки: 2.0107
Теоретическое 1/lambda: 2.0

Среднее выборки — 2.0107 — почти в точности совпало с теоретическим значением 2.0. Расхождение в сотых — это нормальный статистический «шум» конечной выборки; на ещё большем числе экспериментов оно стало бы ещё меньше. Метод сработал: мы взяли равномерные числа и арифметической формулой превратили их в честную экспоненциальную выборку, ни разу не обратившись к специальной функции вроде random.expovariate.

Как работает под капотом

Почему «отражение CDF» вообще даёт правильное распределение? Здесь работает фундаментальный факт теории вероятностей. Возьмём случайную величину $X$ с непрерывной строго возрастающей CDF $F$. Тогда величина $U = F(X)$ равномерно распределена на $[0, 1)$ — этот результат называют интегральным преобразованием вероятности. Метод обратной функции просто читает его «задом наперёд»: если $U$ равномерно, то $X = F^{-1}(U)$ имеет ровно распределение с функцией $F$.

Интуиция — в скорости роста CDF. Там, где плотность распределения высокая, CDF поднимается круто: небольшому шагу по x соответствует большой шаг по вероятности. А поскольку u ложится равномерно по вертикали $[0, 1)$, на крутые (плотные) участки приходится больше попаданий, на пологие (редкие) — меньше. Так равномерность по вертикали превращается в нужную неравномерность по горизонтали.

Маленькая, но важная деталь реализации: в формуле стоит $1 - u$, а не просто $u$. Математически $1 - u$ и $u$ распределены одинаково равномерно, так что обе версии корректны. Но $1 - u$ с random.random() гарантированно лежит в полуинтервале $(0, 1]$, то есть никогда не обращается в ноль, — а $\ln(0)$ не определён. Запись через $1 - u$ страхует от логарифма нуля, если генератор вдруг вернёт ровно 0.0.

Частые ошибки

  • Пытаться применить метод там, где CDF не обращается аналитически. Для нормального распределения обратной CDF в элементарных функциях не существует — приходится использовать численные приближения или совсем другой метод. Главное ограничение приёма: нужна обратимая (хотя бы численно) функция распределения.
  • Подставлять плотность вместо функции распределения. В рецепте участвует именно CDF $F(x)$ (накопленная вероятность), а не плотность $f(x)$. Подстановка плотности даёт бессмысленный результат.
  • Брать $\ln(u)$ и натыкаться на $\ln(0)$. Если random.random() вернёт ровно 0.0, то $\ln(0)$ вызовет ошибку. Запись через $1 - u$ устраняет эту опасность, оставляя аргумент логарифма строго положительным.
  • Путать $\lambda$ и $1/\lambda$. Параметр $\lambda$ — это интенсивность (сколько событий в единицу времени), а среднее экспоненциального распределения равно $1/\lambda$. При $\lambda = 0.5$ среднее равно 2, а не 0.5 — лёгкая, но частая путаница.
  • Забывать про область определения. Экспоненциальная CDF задана для $x \ge 0$; формула $-\ln(1 - u)/\lambda$ при положительном $\lambda$ всегда даёт неотрицательное число — и это правильно. Если знак или область перепутаны, можно получить отрицательные «времена», лишённые смысла.

Итоги

  • Метод обратной функции превращает равномерное u в выборку любого распределения с обратимой CDF: $x = F^{-1}(u)$.
  • Универсальный рецепт: взять CDF, приравнять $F(x) = u$, решить относительно $x$.
  • Для экспоненциального распределения это даёт формулу $x = -\ln(1 - u) / \lambda$; среднее выборки сходится к теоретическому $1/\lambda$.
  • Метод работает, потому что $F(X)$ всегда равномерно (интегральное преобразование вероятности), а крутизна CDF переносит плотность в нужные места.
  • Главное ограничение — нужна обратимая CDF; для нормального распределения, например, аналитической обратной функции нет.
Проверьте себя
1. Что лежит в основе метода обратной функции?
AБерут равномерное u и подставляют его в обратную функцию распределения, получая выборку нужного распределения
BБерут любое число и умножают его на плотность распределения
CСкладывают много равномерных чисел и усредняют их
DГенерируют точки в прямоугольнике и отбрасывают лишние
2. Каким будет правильный порядок шагов рецепта метода обратной функции?
AВзять плотность, продифференцировать её, подставить u
BВзять CDF F(x), приравнять её к равномерному u, решить уравнение относительно x
CСгенерировать точки в прямоугольнике, принять те, что под кривой
DВзять среднее распределения и прибавить случайный шум
3. Почему в формуле для экспоненциального распределения используют ln(1 − u), а не ln(u)?
AПотому что 1 − u даёт другое распределение, чем u
BПотому что ln(u) считается медленнее
CПотому что 1 − u при random.random() строго положителен и страхует от ln(0)
DПотому что иначе результат станет отрицательным
4. Почему метод обратной функции нельзя напрямую применить к нормальному распределению?
AУ нормального распределения нет функции распределения
BЕго CDF не выражается обратимой функцией в элементарных функциях
CНормальное распределение не является случайным
DДля него не существует равномерных чисел
py
Курс по теме
Пройдите курс «Python с нуля» — по шагам, с проверкой
8 уроков · ~14 ч · теория, упражнения и экзамен с бейджем
Открыть курс →