12 KiB
GAN и diffusion: цели, обучение и типичные сбои
Глава сопоставляет состязательное обучение и пошаговое удаление шума, выводит основные цели и разбирает сбои на генерации рукописных символов.
Мотивация и интуиция
VAE задаёт likelihood-bound, но простой pixel decoder часто усредняет допустимые варианты и размывает штрихи. GAN учит generator через discriminator: один создаёт образцы, другой отличает их от данных. Получаются резкие изображения, но обучение может быть нестабильным и пропускать варианты.
Diffusion намеренно постепенно зашумляет данные, а затем учит сеть обращать каждый малый шаг. Обучение стабильно, режимы покрытия обычно лучше, но sampling требует многих вызовов сети. Ни один подход не гарантирует читаемость или правомерность сгенерированного почерка.
GAN: minimax-цель
Пусть \mathbf{z}\sim p(\mathbf{z}), generator
G_{\boldsymbol{\theta}}(\mathbf{z}), discriminator
D_{\boldsymbol{\psi}}(\mathbf{x})\in(0,1). Исходная игра:
\min_G\max_D V(D,G)=
\mathbb{E}_{\mathbf{x}\sim p_{\mathrm{data}}}\log D(\mathbf{x})+
\mathbb{E}_{\mathbf{z}}\log(1-D(G(\mathbf{z}))).
Для фиксированного G оптимизируем каждую точку \mathbf{x}:
p_d(\mathbf{x})\log D(\mathbf{x})+
p_g(\mathbf{x})\log(1-D(\mathbf{x})).
Производная равна p_d/D-p_g/(1-D); приравнивая к нулю, получаем
D^*(\mathbf{x})=\frac{p_d(\mathbf{x})}{p_d(\mathbf{x})+p_g(\mathbf{x})}.
Подстановка даёт
V(D^*,G)=-\log4+2\,D_{JS}(p_d\|p_g), минимум при p_g=p_d.
На практике saturating loss \log(1-D(G(z))) даёт слабый градиент в начале,
поэтому generator минимизирует non-saturating
-\mathbb{E}\log D(G(z)).
Conditional GAN использует label y:
G(\mathbf{z},y), D(\mathbf{x},y). Label embedding можно конкатенировать
с latent, а discriminator строить projection-методом. Условие помогает content,
но style diversity всё ещё требует latent и данных.
Сбои GAN
- Mode collapse: разные
\mathbf{z}дают несколько похожих глифов. - Discriminator dominance: почти идеальный
Dоставляет слабый сигналG. - Oscillation/non-convergence: игроки преследуют меняющиеся цели.
- Artifacts: transpose convolution создаёт checkerboard; resize-convolution часто безопаснее.
- Memorization: резкий образец может быть копией train, особенно на малых данных.
Помогают balanced update rates, spectral normalization, gradient penalty, augmentation, EMA generator weights и проверка diversity. WGAN заменяет JS-подобную цель приближением Wasserstein-1 с 1-Lipschitz critic; Lipschitz ограничивают gradient penalty, а не sigmoid classification.
Forward diffusion
Задаём schedule \beta_t\in(0,1), \alpha_t=1-\beta_t,
\bar\alpha_t=\prod_{s=1}^t\alpha_s:
q(\mathbf{x}_t\mid\mathbf{x}_{t-1})
=\mathcal{N}(\sqrt{\alpha_t}\mathbf{x}_{t-1},\beta_t\mathbf{I}).
Повторная подстановка Gaussian переходов даёт закрытую форму:
\mathbf{x}_t=\sqrt{\bar\alpha_t}\mathbf{x}_0+
\sqrt{1-\bar\alpha_t}\boldsymbol{\varepsilon},\qquad
\boldsymbol{\varepsilon}\sim\mathcal{N}(\mathbf{0},\mathbf{I}).
Значит, для train не нужно последовательно выполнять все t шагов: выбираем
t, семплируем \varepsilon, сразу строим \mathbf{x}_t.
Reverse process и noise prediction
Обратный переход моделируется:
p_{\boldsymbol{\theta}}(\mathbf{x}_{t-1}\mid\mathbf{x}_t)
=\mathcal{N}(\boldsymbol{\mu}_{\boldsymbol{\theta}}(\mathbf{x}_t,t),
\boldsymbol{\Sigma}_t).
Популярная параметризация учит UNet предсказывать добавленный шум:
\mathcal{L}_{simple}=
\mathbb{E}_{\mathbf{x}_0,t,\varepsilon}
\|\varepsilon-\varepsilon_{\boldsymbol{\theta}}(\mathbf{x}_t,t)\|_2^2.
Из оценки шума восстанавливается оценка чистого изображения:
\hat{\mathbf{x}}_0=
\frac{\mathbf{x}_t-\sqrt{1-\bar\alpha_t}\,
\varepsilon_{\boldsymbol{\theta}}(\mathbf{x}_t,t)}
{\sqrt{\bar\alpha_t}}.
Почему noise predictor обычно является UNet
UNet имеет нисходящий путь из convolution/residual-блоков с downsampling,
узкое bottleneck и восходящий путь с upsampling. Skip-связи передают feature
maps одинакового разрешения из encoder в decoder, поэтому bottleneck видит
глобальный контекст, а decoder не теряет точные границы штрихов. Embedding шага
t добавляется в блоки и сообщает уровень шума. Условие класса можно подать
через embedding/adaptive normalization, а текст — через cross-attention.
Свёртки и формы разобраны в модуле 10,
cross-attention — в модуле 13.
Один обратный шаг DDPM
Для noise-prediction параметризации среднее перехода можно записать:
\boldsymbol\mu_\theta(\mathbf x_t,t)=
\frac{1}{\sqrt{\alpha_t}}\left(
\mathbf x_t-\frac{\beta_t}{\sqrt{1-\bar\alpha_t}}
\varepsilon_\theta(\mathbf x_t,t)\right).
Затем семплируют
\mathbf x_{t-1}=\boldsymbol\mu_\theta+\sqrt{\tilde\beta_t}\mathbf z,
где \mathbf z\sim\mathcal N(0,I), а на последнем шаге шум не добавляют.
Полный sampler: начать с \mathbf x_T\sim\mathcal N(0,I), пройти
t=T,T-1,\ldots,1, на каждом шаге предсказать шум и получить
\mathbf x_{t-1}. Формула зависит от выбранной variance/sampler
параметризации, поэтому schedule обучения и sampling-код должны совпадать.
DDPM sampling добавляет рассчитанное среднее reverse posterior и шум на каждом шаге (кроме последнего). DDIM и современные solvers сокращают число шагов, меняя траекторию/аппроксимацию; меньше шагов обычно обменивает скорость на качество.
Conditional diffusion подаёт embedding символа, стиля или reference через concatenation, adaptive normalization или cross-attention. Classifier-free guidance комбинирует unconditional и conditional noise estimates:
\hat\varepsilon=(1+w)\varepsilon_{\rm cond}-w\varepsilon_{\rm uncond}.
Большой w усиливает соответствие условию, но снижает diversity и может
искажать штрихи.
Разобранный пример forward process
Пусть \bar\alpha_t=0.64, скаляр x_0=0.5, шум \varepsilon=-1.
Тогда
x_t=0.8\cdot0.5+0.6\cdot(-1)=-0.2.
Если сеть идеально предскажет -1, то
\hat x_0=(-0.2-0.6(-1))/0.8=0.5. При больших t, когда
\bar\alpha_t\to0, восстановление особенно чувствительно к ошибке noise model.
Runnable PyTorch-код forward diffusion
import torch
T = 100
betas = torch.linspace(1e-4, 0.02, T)
alphas = 1.0 - betas
alpha_bar = torch.cumprod(alphas, dim=0)
x0 = torch.rand(8, 1, 28, 28) * 2 - 1
t = torch.randint(0, T, (x0.shape[0],))
noise = torch.randn_like(x0)
a = alpha_bar[t].view(-1, 1, 1, 1)
xt = a.sqrt() * x0 + (1 - a).sqrt() * noise
# Заглушка показывает интерфейс noise predictor.
model = torch.nn.Conv2d(1, 1, kernel_size=3, padding=1)
pred = model(xt) # реальная модель также обязана получить embedding t
loss = torch.nn.functional.mse_loss(pred, noise)
loss.backward()
print(xt.shape, float(loss))
Одной convolution недостаточно: реальная UNet получает time embedding и имеет receptive field на весь глиф. Этот фрагмент проверяет формулы и формы массива.
Сравнение и оценка
GAN генерирует за один проход, diffusion — итеративно. GAN часто резче при малом размере, diffusion обычно проще оптимизировать и лучше покрывает распределение. Сравнение при разном compute нечестно. FID на natural-image features может плохо соответствовать качеству чёрно-белых глифов; добавляйте accuracy независимого character recognizer, diversity внутри класса, nearest-neighbor к train и человеческую оценку читаемости вслепую.
Типичные ошибки и ограничения
- Считать низкий generator loss признаком качества без просмотра и метрик.
- Обучать discriminator до насыщения или забывать
detachпри его update. - Называть critic WGAN discriminator-вероятностью и добавлять sigmoid.
- Путать
\alpha_tи cumulative\bar\alpha_t. - Не передавать timestep diffusion-модели.
- Использовать schedule/sampler, не соответствующий обученной параметризации
epsilon,x0илиv. - Повышать guidance до потери diversity.
- Не проверять memorization и генерацию имитаций подписей реальных людей.
Краткое резюме
- GAN решает игру generator/discriminator; оптимальный discriminator сравнивает плотности данных и модели.
- Mode collapse и нестабильность требуют независимой оценки diversity.
- Diffusion имеет известный forward Gaussian process и учит обратное denoising.
- Условность усиливает контроль, но не гарантирует читаемость, оригинальность и безопасное применение.
Связанные материалы
- Предыдущая глава: Autoencoder и VAE.
- Следующая глава: Условные глифы и композиция.
- Архитектурная база: CNN и attention.
- Термины: glossary.md.