20 GAN
Контекст
Генеративные модели (порождающие новые данные) обучать трудно: обычно нужно задать явную вероятностную модель и считать неподатливые интегралы нормировки. Ян Гудфеллоу (2014) обходит это радикально — заменяет «посчитать вероятность» на «обмануть критика».
Идея и механизм
Две сети играют в игру с нулевой суммой. Генератор G(z) превращает случайный шум z в образец (например картинку). Дискриминатор D(x) выдаёт вероятность того, что x — настоящий (из данных), а не подделка. Цель — минимакс:
Дискриминатор максимизирует свою точность (верно метить реальное и фейк), генератор минимизирует её (делать фейк неотличимым). Обучение чередует шаги по D и по G. В идеальном равновесии генератор воспроизводит данные, а дискриминатор беспомощен — выдаёт ½ на всё.
теория вероятностей Почему игра ведёт pg к pdata
Шаг 1. Оптимальный дискриминатор. Зафиксируем G. Перепишем цель через распределение генератора pg (заменяя интеграл по шуму):
Подынтегральное — функция вида a log y + b log(1−y). Её максимум по y ∈ (0,1) ищем из производной a/y − b/(1−y) = 0, откуда y* = a/(a+b). Значит оптимальный дискриминатор:
Шаг 2. Что тогда минимизирует G. Подставив D* обратно в V, после преобразования получаем (где JSD — дивергенция Йенсена–Шеннона):
Поскольку JSD ≥ 0 и равна нулю тогда и только тогда, когда pg = pdata, глобальный минимум игры (−log 4) достигается ровно когда генератор воспроизвёл данные. ∎ Так состязание формально заставляет распределение генератора сойтись к распределению данных.
PyTorch Один шаг состязательного обучения
import torch
import torch.nn.functional as F
def gan_step(real, G, D, optD, optG, z_dim):
z = torch.randn(len(real), z_dim)
fake = G(z)
ones, zeros = torch.ones(len(real), 1), torch.zeros(len(real), 1)
# дискриминатор: реальное → 1, фейк → 0
lossD = F.binary_cross_entropy(D(real), ones) + F.binary_cross_entropy(D(fake.detach()), zeros)
optD.zero_grad(); lossD.backward(); optD.step()
# генератор: хочет обмануть D (D(fake) → 1)
lossG = F.binary_cross_entropy(D(fake), ones)
optG.zero_grad(); lossG.backward(); optG.step()
Почему это важно
GAN запустил волну фотореалистичной генерации (лица, super-resolution, перенос стиля) на годы вперёд; Ян Лекун назвал это «самой крутой идеей в ML за 20 лет». Концептуально — это новый способ обучать генеративные модели: не через явную вероятность, а через состязательный сигнал.
Болезни: нестабильность обучения (две сети должны расти синхронно) и mode collapse (генератор выдаёт мало разнообразия). Позже в генерации изображений GAN потеснят диффузионные модели (#46) — более устойчивые и с полным покрытием мод.
Связи
Обе сети — обычные нейросети, обучаемые backprop; нетривиально лишь то, что они оптимизируют противоположные цели одновременно. Градиент дискриминатора по фейку течёт назад в генератор, говоря ему, как стать убедительнее — backprop через две сети сразу.
Та же задача (генерация изображений), но противоположный подход: вместо состязания двух сетей — обучение одной сети постепенно «расшумлять» данные. Диффузия решает фирменные болезни GAN (нестабильность, mode collapse) и к началу 2020-х вытесняет его как доминирующую парадигму генерации картинок.
GAN открыл эру глубокой генеративной модели и научил сообщество думать о генерации как об обучаемом отображении из шума. Линия «text-to-image для всех» продолжится уже на диффузии — но именно GAN сделал саму идею фотореалистичной нейрогенерации мейнстримом.
Что читать в оригинале
Статья короткая и элегантная — стоит прочитать целиком. Минимакс-формулировку и вывод оптимального дискриминатора (см. мат-блок выше) полезно понять — это образец того, как игровую постановку сводят к минимизации статистической дивергенции.