Диффузия и потоки

Score-функция

Градиент логарифма плотности — почему именно он, и как обучить его без знания плотности

Шаг 93 из 117 · ~30 мин

Определение и одно свойство, из которого всё

s(x)=xlogp(x)\htmlData{k=score}{s(x)} = \nabla_x \log p(x)

Градиент берётся по аргументу, а не по параметрам, — в статистике словом score обычно называют другое, и путать их не стоит.

Всё остальное следует из одного наблюдения. Пусть плотность известна только с точностью до множителя: p(x)=p~(x)/Zp(x) = \tilde{p}(x)/Z, где ZZ неизвестно. Тогда

xlogp(x)=x[logp~(x)logZ]=xlogp~(x)\nabla_x \log p(x) = \nabla_x \big[\log \tilde{p}(x) - \log Z\big] = \nabla_x \log \tilde{p}(x)

потому что logZ\log Z — константа по xx. Проверено численно: для гауссианы, умноженной на 137137, score в точке 0.40.4 равен 1.83673469389411541.8367346938941154 — те же цифры, что и без множителя.

Вот в чём ценность. Нормировочная константа — главная техническая трудность вероятностного моделирования: она равна интегралу по всему пространству, и в высокой размерности не считается. Score её не видит. Это и есть причина, по которой генерация формулируется через score, а не через плотность.

Как выглядит score

Для гауссианы всё считается в одну строку:

xlogN(x;μ,σ2)=xμσ2\nabla_x \log \mathcal{N}(x; \mu, \sigma^2) = -\frac{x-\mu}{\sigma^2}

Проверено конечными разностями: при μ=1.3\mu = 1.3, σ=0.7\sigma = 0.7 и x=0x = 0 обе стороны дают 2.6530612242.653061224.

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

Для смеси score равен средневзвешенному по компонентам, с весами-ответственностями:

logp(x)=kwkpk(x)jwjpj(x)ответственность(xμkσk2)\nabla\log p(x) = \sum_k \underbrace{\frac{w_k p_k(x)}{\sum_j w_j p_j(x)}}_{\text{ответственность}} \cdot \left(-\frac{x - \mu_k}{\sigma_k^2}\right)

Проверено на смеси двух гауссиан в пяти точках — совпадение с конечными разностями до 101010^{-10}. Это ровно та формула, по которой виджет считает поле.

сила guidance γ 1
шаг ε 0.05
температура T 1
макс. |score|
17.72
длина пути
98.5
доля времени у мод
32.4%
Стрелки — точный score смеси, длина пропорциональна величине. Обратите внимание, что вдали от данных стрелки длиннее: там плотность падает быстро, и логарифм меняется резко. В нулях плотности score не определён вовсе — и это первая проблема, с которой сталкивается score-based генерация.

Три наблюдения из виджета:

  1. Score растёт вдали от данных. У гауссианы он линеен по расстоянию, и в областях низкой плотности его величина велика. Значит именно там его труднее всего оценить — данных нет.
  2. В нулях плотности score не определён. Логарифма нуля не существует, и никакой сети это не исправить. Это фундаментальная проблема, а не численная.
  3. Между модами поле почти нулевое. Там ответственности сравниваются, и вклады компонент гасят друг друга. Сэмплер, попавший в эту область, не знает, куда идти — и без шума не выберется.

Пункты 1 и 3 объясняют, почему score-based модели не работают без зашумления данных: шум «раздувает» носитель распределения, убирает нули и наполняет пустоты информацией. Урок 060 покажет, что зашумление и есть то, что делает диффузия.

Как обучить score, не зная плотности

Естественная цель — приблизить score сетью sθs_\theta:

L=Ep[12sθ(x)logp(x)2]\mathcal{L} = \mathbb{E}_{p}\Big[\tfrac12\|s_\theta(x) - \nabla\log p(x)\|^2\Big]

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

L=Ep[tr(xsθ(x))+12sθ(x)2]+const\mathcal{L} = \mathbb{E}_{p}\Big[\htmlData{k=trace}{\operatorname{tr}\big(\nabla_x s_\theta(x)\big)} + \tfrac12\|s_\theta(x)\|^2\Big] + \text{const}

Выводится интегрированием по частям; константа не зависит от θ\theta и потому неважна. Проверено численно на гауссиане с μ=1.3\mu = 1.3, σ=0.7\sigma = 0.7 и линейным семейством s(x)=a(xb)s(x) = a(x-b):

aabbпрямая цельХювяринен ++ const
2.041-2.0411.31.30.0000.0000.003-0.003
1.0-1.00.50.50.5860.5860.5830.583
3.0-3.02.02.02.4322.4322.4322.432

Совпадение с точностью Монте-Карло, и минимум обеих целей — в одном месте: a=1/σ2=2.0408a = -1/\sigma^2 = -2.0408, b=μb = \mu.

Но эта цель непрактична. якобиана в размерности dd требует dd обратных проходов — для изображения 256×256×3256\times256\times3 это двести тысяч проходов на один пример. Существуют приближения (sliced score matching, оценка Хатчинсона), но настоящее решение другое, и оно — тема урока 060: если данные зашумлены известным образом, score приближается без следа вовсе.

Итог

  • Score не зависит от нормировочной константы, и это делает его правильным объектом для моделирования.
  • Для гауссианы он линеен и указывает к среднему; для смеси — средневзвешенный по ответственностям.
  • Он не определён в нулях плотности и плохо оцениваем там, где мало данных. Зашумление лечит и то, и другое.
  • Обучать его можно без знания плотности (тождество Хювяринена), но цена — след якобиана, то есть dd проходов. Диффузия обходит эту цену полностью.

Источники

Проверки

0 из 2
  1. Свойства score

    Отметьте все верные утверждения о score-функции.

  2. Score смеси гауссиан

    Реализуйте mixture_score(components, x), где components — список троек [μk,σk,wk][\mu_k, \sigma_k, w_k] (веса в сумме дают единицу). Верните [density, score, first_responsibility, log_density]:

    • density = kwk1σk2πexp ⁣((xμk)22σk2)\sum_k w_k \dfrac{1}{\sigma_k\sqrt{2\pi}} \exp\!\left(-\dfrac{(x-\mu_k)^2}{2\sigma_k^2}\right);
    • score = xlogp(x)\nabla_x \log p(x), посчитанный как средневзвешенное по компонентам: krk(xμkσk2)\sum_k r_k \left(-\dfrac{x-\mu_k}{\sigma_k^2}\right), где rkr_k — доля kk-й компоненты в плотности в точке xx;
    • first_responsibility = r0r_0;
    • log_density = ln(density)\ln(\texttt{density}).

    Проверить себя можно на одной компоненте: там r0=1r_0 = 1, и score обязан равняться xμσ2-\dfrac{x-\mu}{\sigma^2} ровно.

    функция mixture_score

    Загрузка редактора…

    Ctrl/⌘ + Enter