Генеративно-состязательные сети, или GAN, произвели фурор в сообществе ИИ благодаря своей сверхъестественной способности генерировать поразительно реалистичные изображения. Они использовались для создания всего: от потрясающе реалистичных человеческих лиц до произведений искусства, которые выглядят так, будто их нарисовал мастер-художник. Сегодня я предоставлю исчерпывающее руководство о том, что такое GAN и как они работают, дополненное фрагментами кода Python на реальном примере для ясности.
Что такое ГАН?
Изобретенные Яном Гудфеллоу и его командой в 2014 году (бумага), GAN представляют собой тип системы машинного обучения, состоящей из двух основных компонентов: генератора и дискриминатора. Эти две сети работают друг против друга (отсюда и термин состязательный), и именно эта уникальная динамика делает GAN такими эффективными. По мере того, как сети генератора и дискриминатора конкурируют друг с другом, они обе лучше справляются со своими задачами. Генератор учится создавать более реалистичные изображения, а дискриминатор учится точнее определять поддельные изображения.
Генератор создает новые экземпляры данных, а Дискриминатор оценивает их на подлинность; то есть, принадлежит ли каждый экземпляр данных, которые он просматривает, фактическому набору обучающих данных или нет.
Понимание концепции
Концепцию GAN можно понять, рассмотрев аналогию с фальшивомонетчиком (Генератор), пытающимся произвести фальшивую валюту, и полицией (Дискриминатор), пытающейся обнаружить фальшивые деньги. Фальшивомонетчик учится делать деньги более реалистичными с каждой попыткой, в то время как полиция совершенствует свои методы отличить настоящие деньги от подделки.
Что касается GAN, мы хотим, чтобы сеть Генератора производила данные, неотличимые от реальных данных, в то время как Дискриминатор стремится точно классифицировать данные как настоящие или поддельные. GAN использовались для создания самых разных реалистичных изображений, включая лица, животных и объекты.
Пример из реальной жизни: создание лиц с помощью DCGAN
Чтобы воплотить эти идеи в жизнь, давайте рассмотрим реальный пример: глубокая сверточная генеративно-состязательная сеть (DCGAN), обученная генерировать новых знаменитостей после показа фотографий множества реальных знаменитостей. Этот конкретный GAN принимает случайный шум в качестве входных данных и генерирует реалистичные человеческие лица в качестве выходных данных. Для целей этого примера я использую набор данных CelebA от Kaggle.
DCGAN — это особый тип GAN, который использует сверточные слои в своих нейронных сетях, что делает его более подходящим для задач, основанных на изображениях.
Вот схема того, как определить DCGAN:
- Генератор: начинается со слоя, который принимает скрытый вектор (случайный шум), за которым следуют несколько сверточных слоев с повышающей дискретизацией и нормализацией. Выходной слой использует функцию активации
tanh
, которая масштабирует вывод в диапазоне от -1 до 1, что соответствует диапазону наших нормализованных обучающих изображений.
# Generator Code class Generator(nn.Module): def __init__(self, ngpu): super(Generator, self).__init__() self.ngpu = ngpu self.main = nn.Sequential( # input is Z, going into a convolution nn.ConvTranspose2d( nz, ngf * 8, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), # state size. (ngf*8) x 4 x 4 nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), # state size. (ngf*4) x 8 x 8 nn.ConvTranspose2d( ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), # state size. (ngf*2) x 16 x 16 nn.ConvTranspose2d( ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), # state size. (ngf) x 32 x 32 nn.ConvTranspose2d( ngf, nc, 4, 2, 1, bias=False), nn.Tanh() # state size. (nc) x 64 x 64 ) def forward(self, input): return self.main(input)
2. Дискриминатор:Начинается со сверточного слоя, за которым следуют несколько сверточных слоев с субдискретизацией и нормализацией. Выходной слой использует функцию активации sigmoid
, которая масштабирует вывод в диапазоне от 0 до 1, обеспечивая вероятность того, что входное изображение является реальным.
class Discriminator(nn.Module): def __init__(self, ngpu): super(Discriminator, self).__init__() self.ngpu = ngpu self.main = nn.Sequential( # input is (nc) x 64 x 64 nn.Conv2d(nc, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), # state size. (ndf) x 32 x 32 nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplace=True), # state size. (ndf*2) x 16 x 16 nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplace=True), # state size. (ndf*4) x 8 x 8 nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 8), nn.LeakyReLU(0.2, inplace=True), # state size. (ndf*8) x 4 x 4 nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False), nn.Sigmoid() ) def forward(self, input): return self.main(input)
Обучающие изображения:
Механика обучения для GAN
С нашими настройками Дискриминатора (D) и Генератора (G) механика их обучения становится в центре внимания за счет использования определенных функций потерь и оптимизаторов. Примечательным упоминанием здесь является потеря двоичной перекрестной энтропии (часто называемая BCELoss). Для тех, кто склонен искать документацию, PyTorch предлагает обширный обзор.
Для энтузиастов математики функция BCELoss может быть визуализирована как:
l(x, y) = L = {l ₁,….,lₙ}^T
Где:
lₙ = -[yₙ * log xₙ + (1 — yₙ) * log(1-xₙ)]
Прелесть этой функции заключается в ее способности охватывать оба логарифмических компонента целевой функции, а именно log(D(x)) и log(1−D(G(z))). Компонент, который мы выбираем для вычисления из уравнения BCE, определяется входными данными y, которые по сути являются нашими метками Ground Truth (GT).
Для ясности в нашей предстоящей механике обучения:
- Настоящий ярлык определяется как 1
- Поддельный ярлык определяется как 0
Это условное обозначение не является произвольным. Он основан на основополагающем документе GAN и служит основой для расчета потерь D и G.
Чтобы управлять процессами обучения как D, так и G, мы используем два разных оптимизатора Adam. Если вы ссылаетесь на исследовательскую работу DCGAN, вы заметите, что эти оптимизаторы настроены на скорость обучения 0,0002 и значение β1, равное 0,5.
Совет по отслеживанию работы генератора — работать с последовательным пакетом скрытых векторов, обычно взятых из распределения Гаусса (часто называемого fixed_noise). Во время наших обучающих итераций периодическое введение этого fixed_noise в G предлагает интригующее зрелище: вы будете свидетелями того, как изображения постепенно обретают форму из того, что первоначально выглядело как простой шум.
Демистификация обучения GAN
В запутанном танце GAN обучение является ключевым действием, объединяющим все компоненты архитектуры GAN. Но предостережение: лепка GAN — это больше искусство, чем простая арифметика. Пропустите один шаг в гиперпараметрах, и крах режима, которого боятся, может оставить вас без особой подсказки.
Черпая вдохновение из статьи Гудфеллоу, мы собираемся принять Алгоритм 1 и смешать его с идеями известных ганхаков. Два основных указателя оттуда включают:
- Создание отдельных мини-пакетов для реальных и поддельных изображений.
- Настройка цели G для увеличения с помощью logD(G(z)).
Этот путь обучения GAN разделен на два основных сегмента:
1. Воспитание дискриминатора
Основная цель дискриминатора? Поднимите его мастерство в классификации входных данных как реальных или синтетических. Как объясняет Гудфеллоу, цель состоит в том, чтобы «подняться по стохастическому градиенту дискриминатора». По сути, мы стремимся к наилучшему результату log(D(x))+log(1−D(G(z))).
Стратегия? Двунаправленный подход:
- Реальные образцы. Извлеките партию из обучающего набора, дайте ей пройти через D, подсчитайте потери с помощью log(D(x)), и отобразите градиенты в обратном направлении.
- Поддельные образцы: с нашим текущим генератором в качестве мастера создайте поддельную партию, позвольте D проанализировать ее, определите потери с помощью log(1− D(G(z))) и объединить эти градиенты в другом обратном пути.
С градиентами, собранными как из реального, так и из искусственного мира, настало время для оптимизатора Дискриминатора.
2. Лепка генератора
Погружаясь в основополагающий документ, мы видим цель генератора: уменьшить log(1−D(G(z >))) плодить еще более обманчивые фейки. Но, как заметил Гудфеллоу, начинающие ученики часто спотыкаются из-за тусклых градиентов. Обходной путь? Накачайте лог(D(G(z))).
В коде это превращается в:
- Детище генератора с первого шага встречает дискриминатор.
- Мы вычисляем потери G, интригующе используя настоящие ярлыки в качестве исходных данных.
- Градиенты для G отображаются с обратной траекторией.
- И вуаля!! ДНК G (параметры) преображается с помощью оптимизатора.
Парадоксальным на первый взгляд маневром является использование реальных меток для функции потерь. Тем не менее, это мастерский ход. Он манит аспектом log(x) BCELoss, точно согласуясь с нашими намерениями.
# Training Loop # Lists to keep track of progress img_list = [] G_losses = [] D_losses = [] iters = 0 print("Starting Training Loop...") # For each epoch for epoch in range(num_epochs): # For each batch in the dataloader for i, data in enumerate(dataloader, 0): ############################ # (1) Update D network: maximize log(D(x)) + log(1 - D(G(z))) ########################### ## Train with all-real batch netD.zero_grad() # Format batch real_cpu = data[0].to(device) b_size = real_cpu.size(0) label = torch.full((b_size,), real_label, dtype=torch.float, device=device) # Forward pass real batch through D output = netD(real_cpu).view(-1) # Calculate loss on all-real batch errD_real = criterion(output, label) # Calculate gradients for D in backward pass errD_real.backward() D_x = output.mean().item() ## Train with all-fake batch # Generate batch of latent vectors noise = torch.randn(b_size, nz, 1, 1, device=device) # Generate fake image batch with G fake = netG(noise) label.fill_(fake_label) # Classify all fake batch with D output = netD(fake.detach()).view(-1) # Calculate D's loss on the all-fake batch errD_fake = criterion(output, label) # Calculate the gradients for this batch, accumulated (summed) with previous gradients errD_fake.backward() D_G_z1 = output.mean().item() # Compute error of D as sum over the fake and the real batches errD = errD_real + errD_fake # Update D optimizerD.step() ############################ # (2) Update G network: maximize log(D(G(z))) ########################### netG.zero_grad() label.fill_(real_label) # fake labels are real for generator cost # Since we just updated D, perform another forward pass of all-fake batch through D output = netD(fake).view(-1) # Calculate G's loss based on this output errG = criterion(output, label) # Calculate gradients for G errG.backward() D_G_z2 = output.mean().item() # Update G optimizerG.step() # Output training stats if i % 50 == 0: print('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f' % (epoch, num_epochs, i, len(dataloader), errD.item(), errG.item(), D_x, D_G_z1, D_G_z2)) # Save Losses for plotting later G_losses.append(errG.item()) D_losses.append(errD.item()) # Check how the generator is doing by saving G's output on fixed_noise if (iters % 500 == 0) or ((epoch == num_epochs-1) and (i == len(dataloader)-1)): with torch.no_grad(): fake = netG(fixed_noise).detach().cpu() img_list.append(vutils.make_grid(fake, padding=2, normalize=True)) iters += 1
Откровения после тренировки:
По завершении тренировочного балета GAN появляется ряд статистических данных, предлагающих идеи и показатели:
- Loss_D: совокупность путешествий дискриминатора по реальным и поддельным областям, инкапсулированная в log(D(x) )+log(1−D(G(z))).
- Потеря_G: история генератора, рассказанная log(D(G(z) )).
- D(x): средний вердикт дискриминатора для реального мира. Интригующая прогрессия от почти 1, теоретически извивается до 0,5 по мере уточнения G.
- D(G(z)): пара средних значений до и после обновления D для искусственного мира. Начиная с забвения (0), и теоретически танцуя к 0,5, поскольку G находит свой ритм.
Нежное напоминание: эпохи отнимают много времени. Их продолжительность увеличивается или уменьшается в зависимости от их количества и богатства набора данных.
Погружение в результаты обучения GAN
Холст установлен, кисти нарисованы, и теперь пришло время сделать шаг назад и полюбоваться нашим шедевром GAN. Во-первых, давайте посмотрим, как изменились потери D и G во время обучения:
Наконец, давайте взглянем на некоторые реальные изображения и поддельные изображения, созданные нашей моделью GAN.
Обучение GAN представляет собой смесь математической строгости и творческих нюансов. Когда мы путешествуем по его волнам, понимание его течения может стать ключом к раскрытию его потенциала. Удачной тренировки!
Обратите внимание: создание дипфейков и поддельных изображений с использованием GAN может иметь этические и юридические последствия. Эта информация предоставляется в образовательных целях и должна использоваться ответственно.