Теория информации

Практика: от кросс-энтропии до VAE

Собрать блок воедино — вывести CE из MLE, ELBO на бумаге, перплексию руками, VAE на MNIST

Шаг 60 из 117 · ~45 мин

Блок на одной странице

Шесть уроков связаны одним неравенством и одним тождеством. Стоит увидеть их вместе, прежде чем писать код.

E[φ(X)]φ(E[X])(φ выпукла)\htmlData{k=jensen}{\mathbb{E}[\varphi(X)] \ge \varphi(\mathbb{E}[X])} \quad (\varphi \text{ выпукла})
результаткак получается из Йенсена
KL(pq)0\text{KL}(p\|q) \ge 0φ=log\varphi = -\log
H(p,q)H(p)H(p, q) \ge H(p)=H(p)+KL= H(p) + \text{KL}
H(p)lognH(p) \le \log nKL(puniform)0\text{KL}(p\|\text{uniform}) \ge 0
I(X;Y)0I(X;Y) \ge 0KL совместного от произведения
ELBOlogp(x)\text{ELBO} \le \log p(x)φ=log\varphi = \log, вогнута, знак обратный

И одно тождество, из которого выводится вся практика:

logp(x)=ELBO(q)+KL(qp(zx))\log p(x) = \text{ELBO}(q) + \text{KL}\big(q \,\|\, p(z \mid x)\big)

Кросс-энтропия из MLE, на бумаге

Первое упражнение — без кода. Начните с MLE и дойдите до nn.CrossEntropyLoss:

  1. запишите правдоподобие категориального: p(yx)=cqc[y=c]p(y \mid x) = \prod_c q_c^{[y = c]};
  2. возьмите логарифм и минус: получится logqy-\log q_y, то есть только член истинного класса;
  3. усредните по выборке: это H(p^,q)H(\hat p, q) для one-hot p^\hat p;
  4. подставьте q=softmax(z)q = \text{softmax}(z) и убедитесь, что градиент равен qp^q - \hat{p}.

Четвёртый шаг — тот, что был в блоке 2 (урок 080). Смысл упражнения в том, чтобы увидеть: «кросс-энтропия», «минус log-правдоподобие категориального» и «NLL после log_softmax» — три названия одного объекта, и в PyTorch они буквально живут в разных функциях с одним результатом.

Проверьте на виджете, что при one-hot pp кросс-энтропия равна log2qy-\log_2 q_y и от остальных qcq_c не зависит вовсе — выключите все веса pp, кроме одного, и подвигайте qq.

цель p (one-hot)

класс 0 1
класс 1 0
класс 2 0

предсказание q

класс 0 0.5
класс 1 0.3
класс 2 0.2
H(p)
0 бит
H(p, q) — лосс
1 бит
KL(p‖q)
1 бит
KL(q‖p)
бесконечна
H(q)
1.4855 бит

Обратите внимание, что при one-hot цели H(p)=0H(p) = 0, поэтому кросс-энтропия равна KL — весь лосс оказывается «зазором». Это единственный случай, когда «минимум лосса равен нулю» действительно верно, и он же объясняет, почему label smoothing меняет картину: сделав цель не жёсткой, вы поднимаете H(p)H(p) и вместе с ним минимум лосса.

ELBO на бумаге

Второе упражнение — вывести ELBO двумя путями и убедиться, что получается одно.

Путь 1, через Йенсена. logp(x)=logEq[p(x,z)q(z)]Eq[logp(x,z)q(z)]\log p(x) = \log \mathbb{E}_q\left[\frac{p(x,z)}{q(z)}\right] \ge \mathbb{E}_q\left[\log\frac{p(x,z)}{q(z)}\right].

Путь 2, через KL. Начните с KL(qp(zx))0\text{KL}(q \| p(z\mid x)) \ge 0, подставьте p(zx)=p(x,z)p(x)p(z \mid x) = \frac{p(x,z)}{p(x)} и раскройте. Получите то же неравенство и вдобавок точное выражение для зазора.

Второй путь сильнее: он даёт не только границу, но и её погрешность. Проверьте численно на виджете — сумма ELBO и зазора не меняется, куда бы вы ни увели qq.

log p(x) = -0.9163зазорELBO

веса q(z)

z = a 0.333
z = b 0.333
z = c 0.333
log p(x)
-0.91629
ELBO(q)
-1.23909
зазор KL
0.3228

Перплексия руками

Третье упражнение — считать перплексию, не пользуясь библиотекой, на игрушечном корпусе:

  1. возьмите текст из сотни слов, постройте биграммную модель со сглаживанием α=1\alpha = 1;
  2. посчитайте среднюю кросс-энтропию на отложенном тексте в натах, затем перплексию как eHe^H;
  3. пересчитайте в биты и убедитесь, что 2Hбит=eHнаты2^{H_{\text{бит}}} = e^{H_{\text{наты}}};
  4. уберите сглаживание и получите бесконечность на первом же незнакомом биграмме;
  5. сравните перплексию юниграммной и биграммной моделей на одном тексте.

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

VAE на MNIST

Основное упражнение. Минимальный рабочий вариант — энкодер и декодер из двух линейных слоёв каждый, латент размерности 22 (специально маленький, чтобы его можно было нарисовать).

Что реализовать:

  • энкодер выдаёт μ(x)\mu(x) и logσ2(x)\log \sigma^2(x)логарифм дисперсии, чтобы не требовать положительности;
  • репараметризация z=μ+σεz = \mu + \sigma \varepsilon, εN(0,I)\varepsilon \sim \mathcal{N}(0, I);
  • KL к стандартному prior в закрытой форме — её стоит вывести самому:

KL(N(μ,σ2)N(0,1))=12(μ2+σ21logσ2)\text{KL}\big(\mathcal{N}(\mu, \sigma^2) \,\|\, \mathcal{N}(0,1)\big) = \tfrac12\big(\mu^2 + \sigma^2 - 1 - \log \sigma^2\big)

  • реконструкционный член как бинарная кросс-энтропия по пикселям;
  • один сэмпл zz на пример: оценка шумная, но несмещённая.

Что посмотреть после обучения:

  1. нарисуйте латентное пространство. Двумерный zz, раскрашенный по цифрам, — классы должны разделиться, хотя метки в обучении не участвовали;
  2. пройдите по сетке в латенте и декодируйте. Переходы должны быть плавными — это то, чего автоэнкодер без KL-члена не даёт;
  3. следите за двумя членами лосса порознь. Их сумма — не то, что нужно смотреть; интересно именно соотношение;
  4. воспроизведите posterior collapse. Умножьте KL-член на β=10\beta = 10 и убедитесь, что KL уходит в нуль, латент перестаёт зависеть от xx, а реконструкции становятся одинаковыми;
  5. сравните с β=0\beta = 0. Это обычный автоэнкодер: реконструкции резче, а сэмплы из prior — мусор, потому что латентное пространство «дырявое».

Пункты 4 и 5 важнее самого обучения. Они показывают, что ELBO — не произвольная сумма двух слагаемых, а равновесие, и что оба его нарушения выглядят как «модель обучилась», если смотреть только на суммарный лосс.

Что должно сойтись

Числовые ориентиры, по которым видно, что всё правильно:

проверкаожидание
KL-член в начале обученияблизок к нулю (μ0\mu \approx 0, σ1\sigma \approx 1 при малой инициализации)
KL-член после обучениядесятки нат, растёт с размерностью латента
ELBOмонотонно растёт, но не гладко — оценка по одному сэмплу шумная
2Hбит2^{H_{\text{бит}}} против eHнатыe^{H_{\text{наты}}}совпадают до машинной точности
β=10\beta = 10KL 0\to 0, реконструкции вырождаются

И одна вещь, которая не должна сойтись: ELBO вашего VAE и logp(x)\log p(x). Зазор неизвестен и не равен нулю. Если хочется сравнить модели честно, нужна importance-weighted оценка (IWAE) — она берёт kk сэмплов вместо одного и даёт границу тем туже, чем больше kk, сходясь к logp(x)\log p(x) при kk \to \infty.

Источники

Проверки

0 из 2
  1. Блок целиком

    Отметьте все верные утверждения, связывающие уроки этого блока.

  2. Лосс VAE по частям

    Реализуйте vae_terms(mu, logvar, recon, targets) для одного примера с одномерным латентом — верните [kl, reconstruction, elbo] в натах:

    • kl = KL(N(μ,σ2)N(0,1))\text{KL}\big(\mathcal{N}(\mu, \sigma^2) \,\|\, \mathcal{N}(0,1)\big) в закрытой форме, где σ2=elogvar\sigma^2 = e^{\text{logvar}}:

      KL=12(μ2+σ21logσ2)\text{KL} = \tfrac12\big(\mu^2 + \sigma^2 - 1 - \log\sigma^2\big)

    • reconstruction — бинарная кросс-энтропия по пикселям, сумма по всем компонентам, а не среднее:

      i[tilogri+(1ti)log(1ri)]-\sum_i \big[t_i \log r_i + (1 - t_i)\log(1 - r_i)\big]

    • elbo = (reconstruction+kl)-(\text{reconstruction} + \text{kl}).

    Знак в последней строке — та самая ловушка, из-за которой в чужом коде путаются: лосс, который минимизируют, равен reconstruction + kl, а ELBO, которую максимизируют, равна ему с минусом.

    Проверить kl легко: при μ=0\mu = 0 и logvar=0\text{logvar} = 0 она обязана быть ровно нулём, потому что qq тогда совпадает с prior.

    функция vae_terms

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

    Ctrl/⌘ + Enter