Содержание курса 01 / 33

01 / 33 · Основы

Что такое генеративное моделирование

Задача, которую решает курс, — научиться порождать новые данные по примерам из неизвестного распределения; почему это трудно; как компьютер превращает равномерные случайные числа в сэмплы любого распределения и как VAE, GAN, потоки и диффузионные модели подходят к задаче каждый по-своему.

На этой странице 10

Классификатор отвечает на вопрос об объекте: есть ли на картинке кошка? Генеративная модель порождает сам объект: новую картинку с кошкой, которой не было в обучающей выборке. В этой заметке мы точно сформулируем, что это значит, почему это трудно и как остальной курс подходит к задаче.

Задача: примеры из неизвестного распределения#

Будем считать набор данных независимыми сэмплами из некоторого распределения:

x(1),…,x(N)∼pdata(x).x^{(1)}, \dots, x^{(N)} \sim p_{\text{data}}(x).

Для изображений xx — вектор значений пикселей, для текста — последовательность токенов, для звука — сигнал. Распределение pdatap_{\text{data}} неизвестно: никто не может записать плотность вероятности «фотографий кошек». У нас есть только выборка.

Цель — модель pθ(x)p_\theta(x) с параметрами θ\theta, такая что pθ≈pdatap_\theta \approx p_{\text{data}} и из которой можно сэмплировать новые объекты x∼pθx \sim p_\theta. Если приближение хорошее, новые сэмплы похожи на данные, но не копируют их.

Рисунок

Генеративное моделирование: от примеров к модели и обратно к примерам

Данные, модель-смесь и новые сэмплыТри панели. Первая: облако точек в форме двух переплетённых полумесяцев с подписью «p_data неизвестно». Стрелка «подобрать θ» ведёт ко второй: 16 пар концентрических колец вдоль тех же полумесяцев, компоненты смеси гауссиан. Стрелка «сэмплировать» ведёт к третьей: новое облако точек той же формы.данныеx⁽¹⁾, …, x⁽ᴺ⁾ ~ pdataмодельpθ(x) ≈ pdata(x)новые сэмплыx ~ pθpdata неизвестнообучить θсэмплироватьДанные, модель-смесь и новые сэмплыТри панели. Первая: облако точек в форме двух переплетённых полумесяцев с подписью «p_data неизвестно». Стрелка «подобрать θ» ведёт ко второй: 16 пар концентрических колец вдоль тех же полумесяцев, компоненты смеси гауссиан. Стрелка «сэмплировать» ведёт к третьей: новое облако точек той же формы.данныеx⁽¹⁾, …, x⁽ᴺ⁾ ~ pdataмодельpθ(x) ≈ pdata(x)новые сэмплыx ~ pθpdata неизвестнообучить θсэмплировать
Слева — 300 точек из распределения, формулы которого мы не знаем (два зашумлённых полукруга). В центре — модель pθ: здесь равновзвешенная смесь 16 гауссиан с σ = 0.12, расставленных вдоль данных; кольца — линии уровня 1σ и 2σ каждой компоненты. Это иллюстрация того, что даёт обучение, а не результат реального обучения. Справа — 300 новых сэмплов из модели: они похожи на данные, но не совпадают ни с одной исходной точкой.

То есть да: генеративное моделирование — это обучение сэмплированию из распределения, которого мы не знаем, через его приближение по примерам. Отсюда два вопроса. Что значит «приблизить», чтобы можно было обучать? И как компьютер вообще сэмплирует из распределения? Остальная часть заметки отвечает на оба.

Зачем нужна генеративная модель#

Порождение новых объектов — самое заметное применение, но не единственное:

  • Генерация. Новые изображения, тексты, звук, молекулы, похожие на обучающие данные.
  • Условная генерация. Модель p(x∣c)p(x \mid c) порождает данные, соответствующие условию cc: картинку по текстовому запросу, недостающую часть фотографии, версию маленького изображения в высоком разрешении, перевод предложения.
  • Оценка плотности. Если модель умеет вычислять pθ(x)p_\theta(x), плотность — одна из возможных оценок аномальности: низкая плотность может указывать на необычный объект. Эта оценка зависит от представления (плотность меняется при смене единиц или переменных) и от того, насколько хорошо модель подогнана, а высокая плотность в точке — это ни высокая вероятность этой точки, ни гарантия, что объект выглядит типично; эти ограничения разобраны в заметке об обнаружении аномалий. Хорошие модели плотности — ещё и хорошие компрессоры, потому что оптимальный код тратит на xx около −log⁡2p(x)-\log_2 p(x) бит.
  • Обучение представлений. Модели с латентной переменной (VAE) переводят данные в низкоразмерный код, направления которого часто имеют смысл, и учатся этому без разметки.

Что значит «приблизить»: дивергенция, которую минимизируем#

Чтобы обучать pθp_\theta, нужно число, которое говорит, насколько она далека от pdatap_{\text{data}}. Стандартный выбор — расхождение Кульбака–Лейблера, KL-дивергенция (KL divergence) (Энтропия, кросс-энтропия и KL-дивергенция):

DKL(pdata ∥ pθ)=Ex∼pdata[log⁡pdata(x)]−Ex∼pdata[log⁡pθ(x)].D_{\mathrm{KL}}(p_{\text{data}} \,\|\, p_\theta) = \mathbb{E}_{x \sim p_{\text{data}}}\big[\log p_{\text{data}}(x)\big] - \mathbb{E}_{x \sim p_{\text{data}}}\big[\log p_\theta(x)\big].

Первое слагаемое не зависит от θ\theta. Поэтому минимизировать дивергенцию — значит максимизировать математическое ожидание логарифма правдоподобия данных под моделью, а если заменить ожидание средним по выборке, получится метод максимального правдоподобия:

θ⋆=arg⁡max⁡θ1N∑i=1Nlog⁡pθ(x(i)).\theta^\star = \arg\max_\theta \frac{1}{N}\sum_{i=1}^{N} \log p_\theta\big(x^{(i)}\big).

Загвоздка в том, что для этого нужно знать pθ(x)p_\theta(x) как число. Для гибких моделей его часто невозможно вычислить, и семейства моделей в курсе отличаются главным образом тем, как они это обходят: точной формулой, нижней оценкой или вовсе другой целевой функцией.

Курс начинает с этой цели в простейшем случае: двумерная гауссиана подгоняется к гауссовским данным градиентным спуском по KL-дивергенции (Подгонка распределений с помощью KL).

Как компьютер вообще сэмплирует#

Модель генеративна, только если из неё можно сэмплировать. Компьютер не умеет напрямую порождать значения произвольного распределения: всё строится из одного примитива — равномерных случайных чисел — цепочкой преобразований. Весь раздел — одна идея:

Взять распределение, из которого сэмплировать легко, и преобразовать его сэмплы в сэмплы более сложного.

В цепочке пять звеньев, и на них держатся все дальнейшие модели, поэтому пройдём их медленно. Каждый шаг даёт идею, ключевую формулу в рамке, объяснение, почему она работает, и тот же шаг в нескольких строках кода. Кто уже знает сэмплирование обращением функции распределения и методом Бокса — Мюллера, может сразу перейти к разделу почему это трудно и к шести семействам.

Шаг 1: равномерные числа#

Компьютер не умеет подбрасывать монету. Процессор детерминирован: одна и та же программа на одних и тех же входных данных каждый раз делает одно и то же. Случайные числа в программах берутся из двух источников.

  • Физический шум. Операционная система собирает события, которые никто не может предсказать, — точные моменты прерываний от клавиатуры, сети и диска, электрический шум внутри процессора — и смешивает их в общий пул, из которого программы читают (/dev/urandom в Linux и macOS, os.urandom в Python). Эти биты действительно непредсказуемы, но вырабатываются медленно. Их используют для криптографических ключей и для выбора начальной точки, а не для миллионов чисел, нужных в вычислениях.
  • Генераторы псевдослучайных чисел. Короткое детерминированное правило превращает одно число в следующее. Генератор хранит внутреннее состояние ss и при каждом вызове обновляет его, sn+1=T(sn)s_{n+1} = T(s_n), и возвращает число, вычисленное из него. Вся последовательность определяется начальным состоянием, которое задаётся начальным значением генератора (seed). Хороший генератор проходит большие опубликованные наборы статистических тестов; всех мыслимых тестов не проходит ни один генератор: тест, знающий правило, всегда предскажет следующее число.

Игрушечный генератор. Простейшее правило — линейный конгруэнтный генератор (linear congruential generator): умножить, прибавить, взять остаток (Knuth, 1997, §3.2.1). С 16 возможными состояниями:

sn+1=(5 sn+3) mod 16,un=sn/16.s_{n+1} = (5\,s_n + 3) \bmod 16, \qquad u_n = s_n / 16 .

Начнём с начального значения s0=7s_0 = 7. Тогда 5⋅7+3=38=2⋅16+65 \cdot 7 + 3 = 38 = 2 \cdot 16 + 6, значит, s1=6s_1 = 6. Дальше 5⋅6+3=33=2⋅16+15 \cdot 6 + 3 = 33 = 2 \cdot 16 + 1, значит, s2=1s_2 = 1. Продолжая так же, получаем состояния

7, 6, 1, 8, 11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4, 7, …7,\ 6,\ 1,\ 8,\ 11,\ 10,\ 5,\ 12,\ 15,\ 14,\ 9,\ 0,\ 3,\ 2,\ 13,\ 4,\ 7,\ \dots

а деление на 16 даёт числа 0.4375,0.375,0.0625,0.5,0.6875,…0.4375, 0.375, 0.0625, 0.5, 0.6875, \dots Через 16 шагов генератор возвращается к 7 и всё повторяется. По дороге он побывал в каждом состоянии ровно один раз, поэтому за цикл каждое из 16 значений 0,116,…,15160, \tfrac1{16}, \dots, \tfrac{15}{16} встречается ровно один раз и ни одно не предпочтено. Именно это и значит «равномерное» для генератора.

Рисунок

Откуда берутся «случайные» числа

Цикл игрушечного генератора и пары соседних чиселШестнадцать кружков по окружности с номерами состояний 7, 6, 1, 8, 11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4, соединённые стрелками по кругу; кружок 7 выделен. Два квадрата: в первом 16 точек на параллельных наклонных прямых, во втором 400 равномерно разбросанных точек.состояния игрушечного генератора7618111051215149032134начальное значение (seed) 7игрушечный: пары (uₙ, uₙ₊₁)uₙ01хороший генератор: 400 парuₙ01Цикл игрушечного генератора и пары соседних чиселШестнадцать кружков по окружности с номерами состояний 7, 6, 1, 8, 11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4, соединённые стрелками по кругу; кружок 7 выделен. Два квадрата: в первом 16 точек на параллельных наклонных прямых, во втором 400 равномерно разбросанных точек.состояния игрушечного генератора7618111051215149032134начальное значение (seed) 7игрушечныйuₙ01хороший: 400 парuₙ01
Слева — игрушечный генератор s ← (5s + 3) mod 16, запущенный с начального значения 7: он обходит все 16 состояний 7 → 6 → 1 → 8 → … и возвращается к 7, поэтому за цикл каждое значение u = s/16 встречается ровно один раз. Справа — пары соседних чисел (uₙ, uₙ₊₁): у игрушечного генератора они лежат на нескольких параллельных прямых, потому что следующее число жёстко связано с предыдущим; у современного генератора 400 пар заполняют квадрат без видимого узора.

Что меняют настоящие генераторы. У игрушки два видимых недостатка. У неё всего 16 значений, и она повторяется через 16 шагов; к тому же каждое число связано с предыдущим простым правилом, поэтому пары соседних чисел (un,un+1)(u_n, u_{n+1}) ложатся на несколько прямых. Настоящие генераторы сохраняют идею состояния, обновляемого по фиксированному правилу, и устраняют оба недостатка.

  • Огромное состояние. У PCG64, генератора NumPy по умолчанию, 128-битное состояние, и он повторяется лишь через 21282^{128} чисел (O’Neill, 2014). У вихря Мерсенна (Mersenne Twister), генератора PyTorch на CPU, состояние в 19 937 бит и период 219937−12^{19937} - 1 (Matsumoto and Nishimura, 1998).
  • Перемешивающий выход. Перед выдачей состояние перемешивается сдвигами, циклическими сдвигами и умножениями, поэтому между соседними числами не видно никакого узора. PCG64 проходит BigCrush — самую большую батарею тестов библиотеки TestU01, — а вихрь Мерсенна проваливает в ней только два теста линейной сложности (L’Ecuyer and Simard, 2007).
  • Счётчики на GPU. Тысячам потоков GPU числа нужны одновременно, поэтому там PyTorch использует Philox. Он вычисляет nn-е число напрямую как перемешанную функцию от nn и начального значения, так что потокам не нужно ждать друг друга (Salmon et al., 2011).

От целого числа к числу из [0,1)[0, 1). Генератор возвращает 64-битные целые k∈{0,1,…,264−1}k \in \{0, 1, \dots, 2^{64} - 1\}. У числа двойной точности 53-битная мантисса, поэтому библиотеки оставляют старшие 53 бита kk и масштабируют их:

u=⌊k/211⌋⋅2−53.u = \big\lfloor k / 2^{11} \big\rfloor \cdot 2^{-53} .

Получается одно из 2532^{53} равноотстоящих значений в [0,1)[0, 1), и все они равновероятны. С начальным значением 0 первый «сырой» выход NumPy равен k=11 749 869 230 777 074 271k = 11\,749\,869\,230\,777\,074\,271, и формула даёт u=0.63696…u = 0.63696\ldots — ровно то первое число, которое возвращает np.random.default_rng(0).random().

Начальное значение. Начальное значение задаёт начальное состояние: библиотека разворачивает небольшое целое число в полное состояние. То же начальное значение при том же генераторе, тех же вызовах с теми же аргументами в том же порядке и той же сборке библиотеки даёт те же числа — поэтому эксперимент можно повторить. Это узкая гарантия. NumPy сохраняет сырые потоки своих генераторов от версии к версии, но построенные на них сэмплеры, например normal или multivariate_normal, могут меняться между выпусками или сборками (NumPy documentation), а целый запуск обучения на GPU добавляет собственные источники расхождений (обучение на практике). Разные начальные значения тоже не дают независимых потоков автоматически: в игрушечном генераторе выше любое начальное значение попадает на тот же цикл из 16 состояний, так что другое начальное значение лишь сдвигает ту же последовательность. Большие генераторы делают перекрытия очень маловероятными, а для параллельных потоков NumPy выводит дочерние начальные значения через SeedSequence.spawn (NumPy documentation).

В коде. Библиотека делает всё это одним вызовом:

import numpy as np
rng = np.random.default_rng(0)       # начальное значение 0 задаёт всю последовательность
u = rng.random(100_000)              # 100 000 чисел из U(0, 1)
print(u[:3].round(4))                # [0.637  0.2698 0.041 ]
print((u < 0.25).mean())             # 0.25015: четверть из них меньше 0.25

Шаг 2: любое одномерное распределение через обращение функции распределения#

Сначала дискретный пример. Пусть нужен исход aa, bb или cc с вероятностями 0.20.2, 0.50.5 и 0.30.3. Разрежем отрезок [0,1)[0, 1) на три куска ровно таких длин: [0,0.2)[0, 0.2), [0.2,0.7)[0.2, 0.7) и [0.7,1)[0.7, 1). Затем возьмём uu и выдадим исход, в чей кусок оно попало. Равномерное число попадает в кусок с вероятностью, равной его длине, поэтому каждый исход выпадает с нужной вероятностью. Для u=0.637u = 0.637, первого числа из шага 1, получается bb.

Рисунок

Сэмплирование дискретного распределения: разрезать отрезок [0, 1)

Отрезок [0, 1), разрезанный на три куска, ступенчатая функция распределения и доли исходовВертикальная полоса от 0 до 1 разделена на куски длиной 0.2, 0.5 и 0.3 для исходов a, b и c. Ступенчатая функция распределения со ступеньками 0.2, 0.7 и 1. Пунктир от u = 0.637 идёт вправо до ступеньки b и вниз к исходу b. Столбцы долей в 10 000 сэмплах около 0.2, 0.5 и 0.3.abc00.20.71uabcF (накопленная вероятность)u = 0.63710 000 сэмплов: доли0.200a0.503b0.297cОтрезок [0, 1), разрезанный на три куска, ступенчатая функция распределения и доли исходовВертикальная полоса от 0 до 1 разделена на куски длиной 0.2, 0.5 и 0.3 для исходов a, b и c. Ступенчатая функция распределения со ступеньками 0.2, 0.7 и 1. Пунктир от u = 0.637 идёт вправо до ступеньки b и вниз к исходу b. Столбцы долей в 10 000 сэмплах около 0.2, 0.5 и 0.3.abc00.20.71uabcF (накопленная вероятность)u = 0.63710 000 сэмплов: доли0.200a0.503b0.297c
Нужен исход a, b или c с вероятностями 0.2, 0.5 и 0.3. Отрезок [0, 1) режется на куски ровно этих длин в точках 0.2 и 0.7 — это накопленные суммы вероятностей, функция распределения F (ступеньки посередине). Равномерное число u = 0.637 попадает во второй кусок, и выдаётся b. Справа — доли исходов в 10 000 сэмплах: 0.200, 0.503, 0.297; черточки отмечают 0.2, 0.5 и 0.3.

Точки разреза 0.20.2, 0.70.7 и 11 — это накопленные суммы вероятностей Pr⁡(исход≤a)\Pr(\text{исход} \le a), Pr⁡(исход≤b)\Pr(\text{исход} \le b) и Pr⁡(исход≤c)\Pr(\text{исход} \le c), то есть значения функции распределения FF (cumulative distribution function, CDF). Сэмплирование сводится к поиску: найти первый исход, накопленная сумма которого превышает uu.

Непрерывный случай — та же идея с бесконечным числом кусков. У непрерывной случайной величины функция распределения F(x)=Pr⁡(X≤x)F(x) = \Pr(X \le x) плавно растёт от 0 до 1: она говорит, сколько вероятности лежит левее xx. Будем считать её непрерывной и строго возрастающей, чтобы каждый уровень uu достигался ровно в одной точке. Трюк такой: возьмём равномерное uu и спросим, у какого значения xx накопилось ровно столько вероятности. Это значение и есть сэмпл:

 X=F−1(U)∼F где F−1(u) — то x, при котором F(x)=u.\boxed{\,X = F^{-1}(U) \sim F\,} \qquad \text{где } F^{-1}(u) \text{ — то } x \text{, при котором } F(x) = u .

Это метод обратного преобразования (inverse transform sampling; Devroye, 1986, §II.2; формально его разбирает заметка о функциях от случайных величин курса теории вероятностей).

Почему это работает. Зафиксируем любое число xx и вычислим функцию распределения выданного значения в три строки:

Pr⁡(X≤x)=Pr⁡(F−1(U)≤x)=Pr⁡(U≤F(x))=F(x).\begin{aligned} \Pr(X \le x) &= \Pr\big(F^{-1}(U) \le x\big) \\ &= \Pr\big(U \le F(x)\big) \\ &= F(x) . \end{aligned}

Первая строка — определение XX. Вторая верна, потому что FF возрастает: точка, где функция распределения достигает уровня uu, лежит не правее xx ровно тогда, когда uu не больше F(x)F(x). Третья верна, потому что равномерное число не больше F(x)F(x) с вероятностью, равной длине отрезка [0,F(x)][0, F(x)]. Значит, у XX функция распределения FF — ровно то распределение, которое мы хотели.

Разобранный пример: экспоненциальное распределение. У времени ожидания с интенсивностью λ\lambda функция распределения F(x)=1−e−λxF(x) = 1 - e^{-\lambda x} при x≥0x \ge 0. Решить уравнение F(x)=uF(x) = u относительно xx можно в три шага:

1−e−λx=u,e−λx=1−u,−λx=ln⁡(1−u), x=−ln⁡(1−u)λ \begin{aligned} 1 - e^{-\lambda x} &= u, \\ e^{-\lambda x} &= 1 - u, \\ -\lambda x &= \ln(1 - u), \end{aligned} \qquad \boxed{\,x = -\frac{\ln(1 - u)}{\lambda}\,}

При λ=1\lambda = 1:

uu0.10.50.90.99
x=−ln⁡(1−u)x = -\ln(1 - u)0.1050.6932.3034.605

Половина равномерных чисел (u<0.5u < 0.5) попадает в короткий отрезок [0,0.693)[0, 0.693), а последний 1% разбросан по всему, что дальше 4.6. Форма экспоненциального распределения — много коротких ожиданий и длинный хвост долгих — получается сама собой.

В коде. Одна строка превращает равномерные числа в экспоненциальные (log1p(-u) точно вычисляет ln⁡(1−u)\ln(1 - u) при малых uu):

import numpy as np
u = np.random.default_rng(0).random(100_000)
x = -np.log1p(-u)                                 # x = -ln(1 - u): экспоненциальное с интенсивностью 1
print(x.mean().round(3), np.median(x).round(3))   # 0.998 0.691 (теория: 1 и ln 2 = 0.693)

Нормальное распределение. Для стандартного нормального закона FF — это Φ(x)=12(1+erf⁡(x/2))\Phi(x) = \tfrac12\big(1 + \operatorname{erf}(x/\sqrt2)\big). Рецепт тот же, но у Φ−1\Phi^{-1} нет формулы в элементарных функциях. Библиотеки вычисляют её рациональными приближениями с точностью около 16 знаков, например алгоритмом Вичуры (Wichura, 1988). Рисунок показывает, почему рецепт даёт колокол: равные отрезки по uu попадают в короткие отрезки по xx там, где Φ\Phi круто растёт, и в длинные там, где она пологая.

Рисунок

Из равномерного распределения в нормальное через обратную функцию распределения

Обратное преобразование: u из U(0, 1) превращается в x = Φ⁻¹(u)Верхний график: S-образная кривая Φ(x) от 0 до 1 на интервале x от −3.5 до 3.5. На вертикальной оси отмечены шесть значений u: 0.06, 0.24, 0.42, 0.58, 0.8 и 0.95. От каждого идёт горизонтальная пунктирная линия до кривой и затем вертикальная вниз до оси x. Нижний график: гистограмма 4000 значений Φ⁻¹(u) с наложенной колоколообразной плотностью N(0, 1).00.51Φ(x)u ~ U(0, 1)−3−2−10123x4000 значений Φ⁻¹(u)плотность N(0, 1)Обратное преобразование: u из U(0, 1) превращается в x = Φ⁻¹(u)Верхний график: S-образная кривая Φ(x) от 0 до 1 на интервале x от −3.5 до 3.5. На вертикальной оси отмечены шесть значений u: 0.06, 0.24, 0.42, 0.58, 0.8 и 0.95. От каждого идёт горизонтальная пунктирная линия до кривой и затем вертикальная вниз до оси x. Нижний график: гистограмма 4000 значений Φ⁻¹(u) с наложенной колоколообразной плотностью N(0, 1).00.51Φ(x)u ~ U(0, 1)−3−2−10123x4000 значений Φ⁻¹(u)плотность N(0, 1)
Сверху — функция распределения Φ стандартного нормального закона. Шесть равномерных чисел u на вертикальной оси переходят по горизонтали к кривой и опускаются на ось x: x = Φ⁻¹(u). Равные отрезки по u отображаются в короткие отрезки x там, где Φ круто растёт (около нуля), и в длинные на хвостах. Поэтому значения x сгущаются в центре. Снизу — гистограмма 4000 таких значений (ширина столбца 0.25) и плотность N(0, 1): они совпадают.

Шаг 3: стандартное нормальное распределение через преобразование Бокса — Мюллера#

Численное обращение Φ\Phi работает, но медленно. Бокс и Мюллер нашли точный рецепт, который превращает два независимых равномерных числа U1,U2U_1, U_2 в два независимых стандартных нормальных (Box and Muller, 1958). Сначала строим расстояние и угол,

R=−2ln⁡U1,φ=2πU2,R = \sqrt{-2\ln U_1}, \qquad \varphi = 2\pi U_2,

затем переходим к координатам:

Z1=Rcos⁡φ,Z2=Rsin⁡φ.\boxed{\begin{aligned} Z_1 &= R\cos\varphi, \\ Z_2 &= R\sin\varphi . \end{aligned}}

Рецепт выглядит произвольным, но это шаг 2, применённый в полярных координатах. Будем считать (Z1,Z2)(Z_1, Z_2) случайной точкой на плоскости и описывать её углом Θ\Theta и расстоянием RR до начала координат.

  1. Угол равномерен. Совместная плотность двух независимых стандартных нормальных величин равна 12πe−z12/2e−z22/2=12πe−r2/2\frac{1}{2\pi}e^{-z_1^2/2}e^{-z_2^2/2} = \frac{1}{2\pi}e^{-r^2/2}, где r2=z12+z22r^2 = z_1^2 + z_2^2. Она зависит только от расстояния до начала координат, поэтому облако точек круговое. Все направления равновероятны: Θ\Theta равномерно на [0,2π)[0, 2\pi) и не зависит от RR. Угол даёт одно равномерное число: Θ=2πU2\Theta = 2\pi U_2 — это φ\varphi выше.

  2. Квадрат расстояния экспоненциален. Вероятность, что точка окажется на расстоянии не больше t\sqrt t от начала координат, — это интеграл плотности по этому кругу. В полярных координатах элемент площади равен r dr dθr\,dr\,d\theta, а интеграл по углу даёт 2π2\pi и сокращает 12π\frac{1}{2\pi}:

    Pr⁡(R2≤t)=∫02π ⁣ ⁣∫0t12π e−r2/2 r dr dθ=[−e−r2/2]0t=1−e−t/2.\Pr(R^2 \le t) = \int_0^{2\pi}\!\!\int_0^{\sqrt t} \frac{1}{2\pi}\, e^{-r^2/2}\, r\,dr\,d\theta = \Big[-e^{-r^2/2}\Big]_0^{\sqrt t} = 1 - e^{-t/2} .

    Это экспоненциальная функция распределения из разобранного примера с интенсивностью λ=12\lambda = \tfrac12.

  3. Сэмплируем квадрат расстояния по шагу 2. Разобранный пример даёт R2=−ln⁡(1−U1)/12=−2ln⁡(1−U1)R^2 = -\ln(1 - U_1)/\tfrac12 = -2\ln(1 - U_1). Число 1−U11 - U_1 тоже равномерно, поэтому можно взять само U1U_1: R2=−2ln⁡U1R^2 = -2\ln U_1 и R=−2ln⁡U1R = \sqrt{-2\ln U_1}. (Само расстояние RR имеет распределение Рэлея.) В точной арифметике U1=0U_1 = 0 имеет вероятность ноль, но генератор чисел с плавающей точкой на [0,1)[0, 1) может вернуть ровно 0, где логарифм равен −∞-\infty; поэтому код ниже подаёт в логарифм 1−U11 - U_1, которое лежит в (0,1](0, 1].

  4. Возвращаемся к координатам. Z1=Rcos⁡ΘZ_1 = R\cos\Theta и Z2=Rsin⁡ΘZ_2 = R\sin\Theta — это и есть формула в рамке.

Разобранный пример. Возьмём u1=0.3u_1 = 0.3 и u2=0.8u_2 = 0.8.

  • Расстояние: R2=−2ln⁡0.3=2.408R^2 = -2\ln 0.3 = 2.408, значит, R=1.552R = 1.552. Проверка: Pr⁡(R2≤2.408)=1−e−1.204=0.7=1−u1\Pr(R^2 \le 2.408) = 1 - e^{-1.204} = 0.7 = 1 - u_1.
  • Угол: Θ=2π⋅0.8\Theta = 2\pi \cdot 0.8, то есть 288∘288^\circ, при этом cos⁡Θ=0.309\cos\Theta = 0.309 и sin⁡Θ=−0.951\sin\Theta = -0.951.
  • Точка: z=(1.552⋅0.309, 1.552⋅(−0.951))=(0.480,−1.476)z = (1.552 \cdot 0.309,\ 1.552 \cdot (-0.951)) = (0.480, -1.476).
Рисунок

Бокс — Мюллер по шагам: два равномерных числа → точка нормального облака

Преобразование Бокса — Мюллера в три панелиПервая панель: 600 точек, равномерно заполняющих единичный квадрат, точка (0.3, 0.8) выделена. Вторая: из начала координат луч под углом 288° длиной 1.552 с дугой угла. Третья: облако 600 нормальных точек вокруг нуля с окружностями радиусов 1 и 2 и выделенной точкой (0.480, −1.476).1. два равномерных числа(0.3, 0.8)u₁u₂12–3. угол и расстояниеугол 2π·0.8 = 288°2расстояние √(−2 ln 0.3) = 1.55234. точка на плоскостиz = (0.480, −1.476)600 таких точек4Преобразование Бокса — Мюллера в три панелиПервая панель: 600 точек, равномерно заполняющих единичный квадрат, точка (0.3, 0.8) выделена. Вторая: из начала координат луч под углом 288° длиной 1.552 с дугой угла. Третья: облако 600 нормальных точек вокруг нуля с окружностями радиусов 1 и 2 и выделенной точкой (0.480, −1.476).1. два равномерных числа(0.3, 0.8)u₁u₂12–3. угол и расстояниеугол 2π·0.8 = 288°2расстояние √(−2 ln 0.3) = 1.55234. точка на плоскостиz = (0.480, −1.476)600 таких точек4
Слева — пары равномерных чисел (u₁, u₂) в единичном квадрате, пример (0.3, 0.8) выделен. Посередине — та же пара в полярных координатах: u₂ задаёт угол 2πu₂, u₁ задаёт расстояние √(−2 ln u₁). Справа — получившиеся точки z: облако с центром в нуле, круги радиусов 1 и 2. Чем меньше u₁, тем дальше точка: u₁ около нуля даёт редкие точки на хвостах.

В коде. Рецепт даёт стандартное отклонение 1 по обеим координатам и нулевую корреляцию между ними. На практике вызывают нормальный генератор библиотеки, который использует более быстрый вариант того же принципа (в NumPy — метод зиккурата):

import numpy as np
rng = np.random.default_rng(1)
u1, u2 = 1 - rng.random(100_000), rng.random(100_000)   # 1 - U лежит в (0, 1]: логарифм не встретит 0
r, phi = np.sqrt(-2 * np.log(u1)), 2 * np.pi * u2
z1, z2 = r * np.cos(phi), r * np.sin(phi)
print(z1.std().round(3), z2.std().round(3), np.corrcoef(z1, z2)[0, 1].round(3))   # 0.998 1.002 -0.002
print((np.abs(z1) < 1).mean().round(3))   # 0.685: привычные 68% в пределах одного стандартного отклонения

Шаг 4: любая гауссиана через аффинное преобразование#

Шаги 1–3 дают стандартный нормальный шум Z∼N(0,I)Z \sim \mathcal N(\mathbf 0, \mathbf I) — круглое облако с центром в нуле. Гауссиана с любым средним μ\boldsymbol\mu и ковариацией Σ\boldsymbol\Sigma получается из него сдвигом и линейным отображением. Возьмём любую матрицу A\mathbf A с AA⊤=Σ\mathbf A\mathbf A^{\top} = \boldsymbol\Sigma; тогда

 X=μ+AZ∼N(μ,Σ) \boxed{\,X = \boldsymbol\mu + \mathbf A Z \sim \mathcal N(\boldsymbol\mu, \boldsymbol\Sigma)\,}

Среднее сдвигает облако, а матрица растягивает, поворачивает и связывает его координаты. Следующая заметка, Многомерные гауссовские распределения, показывает, как выбрать A\mathbf A и почему это работает.

В коде. Множитель Холецкого — один из допустимых вариантов A\mathbf A:

import numpy as np
mu = np.array([1.0, 2.0])
Sigma = np.array([[2.0, 0.8], [0.8, 1.0]])
A = np.linalg.cholesky(Sigma)                 # нижнетреугольная, A @ A.T == Sigma
z = np.random.default_rng(2).standard_normal((100_000, 2))
x = mu + z @ A.T
print(A.round(3).ravel())                     # [1.414 0.    0.566 0.825]
print(x.mean(axis=0).round(2), np.cov(x.T).round(2).ravel())   # [1. 2.] [2.  0.8 0.8 1. ]

Шаг 5: всё остальное через обучаемое нелинейное отображение#

Гауссианы — это всего лишь эллипсоиды, а у изображений, речи и текста распределения гораздо сложнее эллипса. Чтобы получить форму реальных данных, шум пропускают через нелинейное отображение gg:

 Z∼N(0,I)  ⟶  X=gθ(Z) \boxed{\,Z \sim \mathcal N(\mathbf 0, \mathbf I) \;\longrightarrow\; X = g_\theta(Z)\,}

Распределение XX называют образом (pushforward) распределения шума при отображении gg. В генеративной модели g=gθg = g_\theta — нейронная сеть с параметрами θ\theta, и обучение подбирает θ\theta так, чтобы образ совпал с данными.

Рисунок

Сэмплирование как цепочка преобразований шума

Четыре облака точек: равномерный шум, стандартная нормаль, гауссиана, изогнутое распределениеЧетыре квадратных панели, соединённые стрелками. Первая: точки, равномерно заполняющие единичный квадрат. Стрелка «Бокс — Мюллер» ведёт ко второй: круглое облако с центром в нуле. Стрелка «μ + A z» ведёт к третьей: то же облако, растянутое по горизонтали и сжатое по вертикали. Стрелка «g» ведёт к четвёртой: облако, изогнутое в дугу. Одна выделенная точка отмечена во всех четырёх панелях.равномерный шумu ~ U(0, 1)²стандартная нормальz ~ N(0, I)любая гауссианаμ + Az ~ N(μ, AAᵀ)сложная формаx = g(μ + Az)Бокс —Мюллерμ + A zg(·)Четыре облака точек: равномерный шум, стандартная нормаль, гауссиана, изогнутое распределениеЧетыре квадратных панели, соединённые стрелками. Первая: точки, равномерно заполняющие единичный квадрат. Стрелка «Бокс — Мюллер» ведёт ко второй: круглое облако с центром в нуле. Стрелка «μ + A z» ведёт к третьей: то же облако, растянутое по горизонтали и сжатое по вертикали. Стрелка «g» ведёт к четвёртой: облако, изогнутое в дугу. Одна выделенная точка отмечена во всех четырёх панелях.равномерный шумu ~ U(0, 1)²стандартная нормальz ~ N(0, I)любая гауссианаμ + Az ~ N(μ, AAᵀ)сложная формаx = g(μ + Az)Бокс —Мюллерμ + A zg(·)
Одни и те же 320 пар равномерных чисел проходят все шаги; янтарная точка — одна пара, прослеженная до конца. Бокс — Мюллер превращает квадрат в круглое стандартное нормальное облако. Аффинное отображение μ + Az с A = diag(1.4, 0.45) и μ = 0 даёт гауссиану с ковариацией AAᵀ. Нелинейное отображение g(y) = (y₁, y₂ − 0.45 y₁² + 0.9) изгибает эллипс в дугу — распределение, которое уже не гауссово. В генеративной модели последний шаг — обучаемая сеть gθ.

Это центральная картина курса: генеративная модель — это простой шум плюс обучаемое преобразование. У прямого генератора сэмплировать легко по построению: берём шум и один раз применяем сеть; некоторые семейства ниже платят за каждый сэмпл больше, например цепочкой из многих вызовов сети. Трудно в обратную сторону: подобрать θ\theta, для чего нужен способ сравнить образ шума с данными.

Если gg обратимо и гладко, у плотности XX даже есть формула — замена переменных (change of variables)

pX(x)=pZ(g−1(x)) ∣det⁡∂g−1(x)∂x∣,p_X(x) = p_Z\big(g^{-1}(x)\big)\,\left|\det \frac{\partial g^{-1}(x)}{\partial x}\right|,

с которой начинаются нормализующие потоки. Для произвольной сети gθg_\theta такой формулы нет, и именно здесь семейства моделей расходятся.

Почему это трудно#

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

  • Размерность. Цветное изображение 256×256256 \times 256 — это 256⋅256⋅3=196 608256 \cdot 256 \cdot 3 = 196\,608 чисел. Плотность на таком пространстве нельзя задать таблицей, и общей практичной процедуры сэмплирования обращением нет. Совместная функция распределения существует, но единственного обращения у неё нет; сэмплирование по координатам потребовало бы всех условных функций распределения, а их-то мы и не знаем.
  • Концентрация. Естественные изображения занимают крошечную часть этого пространства — окрестность поверхности гораздо меньшей размерности (многообразия): почти любой случайный массив пикселей — шум. Модель должна поместить почти всю вероятность на это тонкое множество.
  • Нормировка. Гибкая функция fθ(x)≥0f_\theta(x) \ge 0 становится плотностью только после деления на ∫fθ(x) dx\int f_\theta(x)\,dx — интеграл по всему пространству, который для нейронных сетей невычислим. Без него правдоподобие в методе максимального правдоподобия посчитать нельзя.

Шесть обходных путей#

Каждое семейство в курсе сохраняет картину «шум плюс преобразование» и отличается тем, чем жертвует, чтобы обучение стало возможным.

Рисунок

Шесть семейств: шум превращается в данные, отличается то, что можно посчитать

Схема шести семейств генеративных моделейШесть строк: VAE, GAN, поток, модель, основанная на энергии, диффузия, авторегрессионная модель. В каждой слева кружок с шумом, стрелка в синий прямоугольник с обучаемым преобразованием и стрелка к объекту; справа написано, что модель позволяет посчитать. Под строками пунктиром показаны вспомогательные части: энкодер у VAE, дискриминатор у GAN, обратное преобразование у потока, неизвестная нормировка у EBM, фиксированное зашумление у диффузии, порядок по одному значению у авторегрессионной модели.простой шумобученное преобразованиеобъектчто можно посчитатьVAEzдекодер pθ(x | z)xнижняя оценка log p(x)энкодер qφ(z | x)GANzгенератор Gθ(z)xсэмплы без плотностидискриминатор: настоящий?Потокzобратимое fθxточное log p(x)fθ⁻¹ обратноEBMx0K шагов Ланжевена по −∇EθxKlog p(x) до константыZ неизвестна; MCMC и при обученииДиффузияxTT шагов шумоподавленияx₀нижняя оценка log p(x)шум добавляют по известному правилуАвторегр.upθ(xi | x<i), i = 1…nxточное log p(x)по одному значению, n шаговСхема шести семейств генеративных моделейШесть блоков друг под другом: VAE, GAN, поток, модель, основанная на энергии, диффузия, авторегрессионная модель. В каждом: кружок с шумом, синий прямоугольник с обучаемым преобразованием, кружок с объектом; ниже — вспомогательная часть и то, что модель позволяет посчитать.VAEzдекодер pθ(x | z)xэнкодер qφ(z | x)считается: нижняя оценка log p(x)GANzгенератор Gθ(z)xдискриминатор: настоящий?считается: сэмплы без плотностиПотокzобратимое fθxfθ⁻¹ обратносчитается: точное log p(x)EBMx0K шагов Ланжевена по −∇EθxKZ неизвестна; MCMC и при обучениисчитается: log p(x) до константыДиффузияxTT шагов шумоподавленияx₀шум добавляют по известному правилусчитается: нижняя оценка log p(x)Авторегр.upθ(xi | x<i), i = 1…nxпо одному значению, n шаговсчитается: точное log p(x)
Каждое семейство порождает объект, преобразуя простой шум; синим — то, что обучается. VAE учит декодер вместе с энкодером, и их обучение даёт нижнюю оценку логарифма правдоподобия (ELBO). GAN учит генератор в паре с дискриминатором и плотности не даёт вовсе. Поток использует обратимое преобразование, поэтому плотность считается точно по формуле замены переменных. Модель, основанная на энергии (EBM), учит только энергию E_θ(x), плотность пропорциональна exp(−E_θ(x)) с неизвестной нормировкой Z, а сэмплы получаются шумной цепью Маркова: шаг вниз по градиенту энергии плюс случайный толчок, так что цепь исследует распределение, а не минимизирует энергию, и ей может понадобиться много шагов. Диффузионная модель учится шаг за шагом обращать зашумление, которое задано заранее, и тоже оптимизирует нижнюю оценку. Авторегрессионная модель превращает равномерные числа u в значения по одному, каждое из обученного условного распределения при уже полученных значениях, и её правдоподобие точное: произведение этих условных распределений.
СемействоКак получить сэмплЧто обучаетсяЧто можно посчитатьЗаметки курса
Вариационный автоэнкодер (VAE)z∼N(0,I)z \sim \mathcal N(\mathbf 0, \mathbf I), затем декодердекодер pθ(x∣z)p_\theta(x \mid z) и энкодер qϕ(z∣x)q_\phi(z \mid x)нижнюю оценку log⁡pθ(x)\log p_\theta(x), ELBOVAE
Генеративно-состязательная сеть (GAN)z∼p(z)z \sim p(z), затем x=Gθ(z)x = G_\theta(z) за один проходгенератор Gθ(z)G_\theta(z), обучаемый против дискриминатораплотности нет, только сэмплыGAN, WGAN, масштабирование, StyleGAN, α-GAN, CycleGAN, StarGAN, за пределами CycleGAN
Нормализующий поток (normalizing flow)z∼N(0,I)z \sim \mathcal N(\mathbf 0, \mathbf I), затем цепочка обратимых отображенийобратимое fθf_\theta с вычислимым якобианомточное log⁡pθ(x)\log p_\theta(x)Потоки, архитектуры
Модель, основанная на энергии (energy-based model, EBM)начать с шума и вести шумную марковскую цепь (Ланжевена), исследующую e−Eθe^{-E_\theta}; шагов много, перемешивание может быть медленнымэнергия Eθ(x)E_\theta(x), причём pθ(x)=e−Eθ(x)/Zθp_\theta(x) = e^{-E_\theta(x)}/Z_\thetalog⁡pθ(x)\log p_\theta(x) с точностью до неизвестной константы log⁡Zθ\log Z_\theta; сэмплы — через MCMCСэмплеры MCMC, EBM
Диффузионная модель (DDPM)начать с шума xTx_T и шаг за шагом очищать его до x0x_0сеть, обращающая один малый шаг зашумлениянижнюю оценку log⁡pθ(x)\log p_\theta(x)Прямой процесс, обучение, score matching, латентная диффузия, редактирование перетаскиванием
Авторегрессионная модельпо одному значению или токену, каждое при уже полученныхусловные распределения pθ(xi∣x<i)p_\theta(x_i \mid x_{<i}), каждое — softmax по значениям или токенамточное log⁡pθ(x)\log p_\theta(x)PixelRNN и PixelCNN, трансформеры

Последняя заметка переносит эти инструменты с изображений на звук: поток превращает текст в речь, а авторегрессионный трансформер над токенами нейросетевого аудиокодека генерирует музыку.

Поскольку не каждая модель даёт правдоподобие, а правдоподобие не всегда отражает, насколько хороши сэмплы на вид, в курсе есть и метрики, которые считаются только по сэмплам: Inception Score, FID, LPIPS, точность и полнота и оценка людьми.

План курса#

  1. Основы. Гауссианы и сэмплирование, энтропия и KL-дивергенция, подгонка распределения минимизацией KL, база для обучения сетей (батч-нормализация, градиенты, свёртки) и автоэнкодеры.
  2. Модели с латентными переменными. VAE: латентный код, нижняя оценка правдоподобия и трюк репараметризации; β-VAE и дискретные коды VQ-VAE.
  3. Состязательные модели. GAN, дивергенция Йенсена–Шеннона, которую они неявно минимизируют, и GAN Вассерштейна; масштабирование GAN (ProGAN, SAGAN, BigGAN) и управление ими (StyleGAN); автоэнкодеры, обучаемые состязательными функциями потерь (α-GAN); перевод изображений без парных примеров (CycleGAN), между многими доменами (StarGAN) и дальше (MUNIT, CUT, SPADE, сверхразрешение).
  4. Оценка качества. IS, FID и LPIPS, точность и полнота, оценка людьми.
  5. Точное правдоподобие. Нормализующие потоки и архитектуры, в которых якобиан дёшев.
  6. Модели, основанные на энергии. Сэмплеры для плотностей, известных с точностью до константы (MALA, гамильтонов Монте-Карло, NUTS), и модели, основанные на энергии, обучаемые контрастивной дивергенцией.
  7. Диффузия. Прямой процесс зашумления и целевая функция обучения DDPM, score matching и динамика Ланжевена, латентная диффузия и Stable Diffusion, редактирование перетаскиванием точек с DragGAN и DragDiffusion.
  8. Авторегрессионные модели. Цепное правило как модель, PixelRNN и PixelCNN, трансформеры над пикселями и токенами изображений.
  9. Звук. Синтез речи по тексту с помощью потока и музыка от авторегрессионного трансформера над токенами нейросетевого кодека.

Что нужно запомнить#

  • Данные — выборка из неизвестного pdatap_{\text{data}}; генеративная модель — это pθp_\theta, из которого можно сэмплировать и которое подогнано так, что pθ≈pdatap_\theta \approx p_{\text{data}}.
  • Минимизация DKL(pdata ∥ pθ)D_{\mathrm{KL}}(p_{\text{data}} \,\|\, p_\theta) — это метод максимального правдоподобия, и ему нужно pθ(x)p_\theta(x) как число.
  • Любое сэмплирование начинается с равномерных чисел: обращение функции распределения в одномерном случае, Бокс — Мюллер для нормальных величин, аффинное отображение для любой гауссианы, обучаемое нелинейное отображение для всего остального.
  • Каждая модель курса — шум плюс обучаемое преобразование; семейства отличаются тем, что умеют посчитать: оценку снизу (VAE, диффузия), ничего (GAN), правдоподобие с точностью до константы (модели, основанные на энергии) или точное правдоподобие (потоки, авторегрессионные модели).

Дальше#

Первая ступень лестницы, на которой строится курс, — гауссиана: как её ковариация задаёт форму данных и как аффинное отображение превращает стандартный шум в любую гауссиану. Это заметка Многомерные гауссовские распределения.

Литература#