Перейти к содержимому
BIASED

Трюк репараметризации

Почему через сэмплирование не проходит градиент и как одна строчка z = μ + σ ⊙ ε это чинит.

Что выдаёт энкодер

Обычный автокодировщик переводит вход xx в одну точку zz и обратно. Реконструирует он хорошо, но генератором не является: возьмите случайную точку латентного пространства — и у декодера нет причин выдать что-то осмысленное.

Вариационный автокодировщик (Kingma and Welling, 2014) упорядочивает латентное пространство. Каждый вход кодируется в гауссиану, а не в точку:

qφ(z∣x)=N(μφ(x), σφ2(x)),q_\varphi(z \mid x) = \mathcal N\big(\mu_\varphi(x),\ \sigma^2_\varphi(x)\big),

и каждая такая гауссиана подтягивается к одному фиксированному априорному распределению p(z)=N(0,I)p(z) = \mathcal N(0, I). В коде энкодер заканчивается двумя «головами»: одна для μ\mu, другая для log⁡σ2\log\sigma^2.

Где обрывается градиент

Обучение максимизирует ELBO, а его слагаемое реконструкции — это матожидание по кодам:

Eqφ(z∣x)[log⁡pθ(x∣z)].\mathbb E_{q_\varphi(z \mid x)}\big[\log p_\theta(x \mid z)\big].

Оцениваем его одним сэмплом на вход: берём z∼N(μ,σ2)z \sim \mathcal N(\mu, \sigma^2), декодируем, сравниваем с xx. Функция потерь явно зависит от μ\mu и σ\sigma — сдвинете μ\mu, и коды сдвинутся вместе с ним.

Но в вычислительном графе дыра. Шаг «вытащить случайное число из N(μ,σ2)\mathcal N(\mu, \sigma^2)» возвращает значение без производной: градиент функции потерь доходит до zz и останавливается. Декодер учится, а энкодер, задача которого — выдавать μ\mu и σ\sigma, от слагаемого реконструкции не получает ничего.

Вынести случайность наружу

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

z=μ+σ⊙ε,ε∼N(0,I).z = \mu + \sigma \odot \varepsilon, \qquad \varepsilon \sim \mathcal N(0, I).

Распределение то же, что и раньше: сдвиг стандартной гауссианы на μ\mu и масштаб на σ\sigma дают ровно N(μ,σ2)\mathcal N(\mu, \sigma^2). Модель не изменилась — изменилось только место, где живёт случайность.

Энкодер выдаёт log⁡σ2\log\sigma^2, а не σ\sigma, поэтому перед сдвигом и масштабом σ\sigma нужно из него восстановить. В PyTorch шум даёт torch.randn_like — он сэмплирует ε\varepsilon той же формы, что и аргумент.

Градиенты теперь проходят

Теперь zz — обычная дифференцируемая функция μ\mu и σ\sigma, а ε\varepsilon играет роль фиксированного входа:

∂zj∂μj=1,∂zj∂σj=εj.\frac{\partial z_j}{\partial \mu_j} = 1, \qquad \frac{\partial z_j}{\partial \sigma_j} = \varepsilon_j .

Градиенты слагаемого реконструкции проходят через zz обратно в энкодер (Kingma and Welling, 2014, §2.4). Autograd сделает это сам — важно, что теперь ему есть что дифференцировать.

Есть и менее заметный выигрыш. Универсальная альтернатива — оценка через градиент логарифма плотности (score-function, REINFORCE) — тоже даёт несмещённые градиенты без репараметризации, но её дисперсия обычно гораздо выше. Репараметризованная оценка использует производную самой функции потерь, а она несёт куда больше информации на один сэмпл.

Почему log σ², а не σ

Почему энкодер предсказывает log⁡σ2\log\sigma^2, а не σ\sigma? Стандартное отклонение обязано быть положительным, а последний линейный слой сети может выдать любое число. Предсказание логарифма снимает ограничение бесплатно:

σ=exp⁡(12log⁡σ2)>0при любом выходе.\sigma = \exp\big(\tfrac12 \log\sigma^2\big) > 0 \quad \text{при любом выходе}.

Заодно очень маленькие дисперсии остаются в удобном численном масштабе. Частая альтернатива — softplus; сам трюк работает с любой положительной σ\sigma.

KL без сэмплирования

Вторая половина функции потерь, DKL(qφ(z∣x) ∥ p(z))D_{\mathrm{KL}}\big(q_\varphi(z \mid x) \,\|\, p(z)\big), вообще не требует сэмплирования. Между двумя гауссианами у неё есть замкнутая форма; в сумме по dd латентным координатам

LKL=−12∑j=1d(1+log⁡σj2−μj2−σj2).\mathcal L_{\mathrm{KL}} = -\frac12 \sum_{j=1}^{d} \big(1 + \log \sigma_j^2 - \mu_j^2 - \sigma_j^2\big).

Она равна нулю ровно тогда, когда μj=0\mu_j = 0 и σj=1\sigma_j = 1 во всех координатах, то есть когда апостериорное распределение совпадает с априорным. В коде это одна строка, и она дифференцируется по μ\mu и log⁡σ2\log\sigma^2 напрямую.

Собираем шаг обучения

Всё вместе — один шаг обучения:

mu, logvar = encoder(x)
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
z = mu + std * eps
x_hat = decoder(z)
 
recon = ((x - x_hat) ** 2).sum(dim=1)
kl = -0.5 * (1 + logvar - mu**2 - logvar.exp()).sum(dim=1)
loss = (recon + kl).mean()
loss.backward()

Квадратичная ошибка соответствует гауссовскому декодеру с фиксированной дисперсией s2=12s^2 = \tfrac12 — тогда это отрицательный ELBO с точностью до константы. Другая фиксированная дисперсия меняет вес KL-слагаемого — на этой идее построен β-VAE.

Проверьте себя

Те же вопросы, что и в Pulse. Ответы общие для обоих режимов.

Что выдаёт энкодер

Вопрос· 1

Что энкодер VAE выдаёт для одного входа xx?
Выберите один ответ

Где обрывается градиент

Вопрос· 2

Почему нельзя просто сделать backprop через z∼N(μ,σ2)z \sim \mathcal N(\mu, \sigma^2)?
Выберите один ответ

Вынести случайность наружу

Вопрос· 3

Энкодер выдал mu и logvar =log⁡σ2= \log\sigma^2, шум eps ∼N(0,I)\sim \mathcal N(0, I) уже сгенерирован. Запишите zz.

Переменные: mu, logvar, eps (можно std, sigma). Функции: exp, sqrt, log. Пример: mu + 2*eps

Вынести случайность наружу

Вопрос· 4

Одномерный случай: μ=2\mu = 2, log⁡σ2=ln⁡4\log\sigma^2 = \ln 4, ε=−0.75\varepsilon = -0.75. Чему равен zz?

Градиенты теперь проходят

Вопрос· 5

Что верно после репараметризации z=μ+σ⊙εz = \mu + \sigma \odot \varepsilon? Отметьте все верные утверждения.
Выберите все верные

Выберите все верные

Градиенты теперь проходят

Вопрос· 6

Энкодер выдаёт s=log⁡σ2s = \log\sigma^2. При σ=2\sigma = 2 и ε=−0.75\varepsilon = -0.75 найдите ∂z/∂s\partial z / \partial s.

Почему log σ², а не σ

Вопрос· 7

Почему энкодер обычно предсказывает log⁡σ2\log\sigma^2, а не σ\sigma напрямую?
Выберите один ответ

KL без сэмплирования

Вопрос· 8

Одно измерение: μ=1\mu = 1, σ2=1\sigma^2 = 1. Чему равен DKL(N(μ,σ2) ∥ N(0,1))D_{\mathrm{KL}}(\mathcal N(\mu, \sigma^2) \,\|\, \mathcal N(0, 1))?

Собираем шаг обучения

Вопрос· 9

Расставьте шаги одного прохода VAE в правильном порядке.
Расставьте по порядку
  1. 1Сэмплируем ε∼N(0,I)\varepsilon \sim \mathcal N(0, I)
  2. 2z=μ+σ⊙εz = \mu + \sigma \odot \varepsilon
  3. 3σ=exp⁡(12log⁡σ2)\sigma = \exp(\tfrac12\log\sigma^2)
  4. 4Потери: ∥x−x^∥2+DKL\|x - \hat x\|^2 + D_{\mathrm{KL}}
  5. 5Декодер: z↦x^z \mapsto \hat x
  6. 6Энкодер: x↦(μ,log⁡σ2)x \mapsto (\mu, \log\sigma^2)

Подробный конспект

Ответы сохраняются на сервере и привязаны к этому браузеру, пока вы не войдёте.