Как и в предыдущем блоге, вот ссылка на репозиторий GitHub, чтобы увидеть код в действии. При нажатии на блокнот автоматически отображаются результаты, но его также легко запустить самостоятельно.



Общие состязательные сети, также известные как GAN, представляют собой полезную архитектуру для создания изображений с нуля. Примеры использования GAN включают веб-сайт игровой площадки Nvidia: https://www.nvidia.com/en-us/research/ai-playground/. Здесь они приводят примеры использования GAN, начиная от старения вашего питомца и заканчивая созданием подробных произведений искусства из простых иллюстраций. В этом проекте я использовал GAN для синтетического создания человеческих лиц. Как и в случае с предыдущим блогом, этот тип проекта может способствовать лучшему пониманию человеческого разума. Генерация лиц, особенно с эмоциями, может помочь нам понять, что мы ищем, когда речь идет об эмоциях на человеческом лице. Хотя текущий проект не может генерировать лица высокого разрешения с эмоциями, это шаг в этом направлении.

Что такое ГАН?

Как упоминалось ранее, GAN — это архитектура для создания образов с нуля. Они состоят из двух разных нейронных сетей, дискриминатора и генератора. Генератор должен генерировать случайный шум до тех пор, пока дискриминатор не сможет различить лицо среди шума. Дискриминатор также определяет, является ли лицо реальным или фальшивым, на основе своих собственных данных предыдущего обучения. По сути, они обучают друг друга: дискриминатор обучает генератор создавать более реалистичные лица, а генератор обучает дискриминатор лучшему распознаванию реальных лиц.

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

Данные

Как видно на рисунке выше, GAN все еще нужны реальные данные, чтобы дискриминатор мог определить, является ли изображение поддельным или реальным. Для этих данных нам нужен набор, который имеет множество граней. Labeled Faces in the Wild имеет разнообразный набор данных о лицах, различающихся по этнической принадлежности и возрасту, что делает его лучшим набором данных при создании лиц с разным разнообразием. В приведенном выше репозитории Github показан процесс загрузки данных.

Создание генератора

Генератор по сути является обратной CNN. В CNN мы переходим от большого набора данных к тому, который сжимается с использованием ядерных и матричных операций. В генераторе мы хотим сделать обратное. Мы создадим модель, которая начинается со случайного шума, а затем повышает его дискретизацию до тех пор, пока он не станет достаточно большим для представления изображения. Я выбрал разрешение 100 на 100 просто потому, что повышение разрешения занимало значительно больше времени. Еще раз, код для генератора находится в ссылке Github, но я также выложу его здесь.

def make_generator_model():
model = tf.keras.Sequential()
model.add(layers.Dense(25*25*256, use_bias=False, input_shape=(noise_dim,)))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Reshape((25, 25, 256)))
assert model.output_shape == (None, 25, 25, 256)
model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
assert model.output_shape == (None, 25, 25, 128)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
assert model.output_shape == (None, 50, 50, 64)
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Conv2DTranspose(3, (5, 5), strides=(2,2), padding='same', use_bias=False, activation='tanh'))
assert model.output_shape == (None, 100,100,3)
return model

Строки утверждения использовались просто для отладки, чтобы убедиться, что математика, стоящая за размерами размеров, была правильной (мне потребовалось довольно много времени, чтобы понять это правильно). Кроме этого, все остальное использует слои Keras Dense с измененными параметрами для масштабирования изображения. Conv2DTranspose — это задача повышения частоты дискретизации, а LeakyRelu — активация скрытых слоев, но для последнего слоя это tanh (это я узнал исключительно из чтения об архитектуре GAN). В любом случае, конечным результатом является изображение бессмысленного шума размером 100 на 100.

Ясно, что это далеко не различит лицо, но мы надеемся, что оно будет с дискриминатором и достаточным количеством эпох.

Создание дискриминатора

Дискриминатор намного проще сделать и понять. По сути, это классификатор CNN. Архитектура модели состоит из одного входного слоя, двух скрытых слоев и выходного слоя. Активации для слоев просто негерметичны. Результатом модели будет простое число, вероятность того, что введенное изображение размером 100 на 100 будет реальным или поддельным. Код:

def make_discriminator_model():
model = tf.keras.Sequential()
model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
input_shape=[100, 100, 3]))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3))
model.add(layers.Flatten())
model.add(layers.Dense(1))
return model

Обучение

Все, что осталось сделать, это использовать две модели и обучить их друг против друга, как описано ранее. Для этого нам нужно определить функцию потерь. Для задачи я выбрал бинарную кросс-энтропию.

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real_output, fake_output):
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
total_loss = real_loss + fake_loss
return total_loss
def generator_loss(fake_output):
return cross_entropy(tf.ones_like(fake_output), fake_output)

Для потери дискриминатора 1 — это реальное изображение, а 0 — поддельное изображение. Если бы дискриминатор должен был правильно определить, является ли изображение реальным или поддельным, он выдал бы массив из 1 или 0 в правильной форме. Использование массива, который будет создан с кросс-энтропийной потерей, а затем сложение их вместе дает нам реальную потерю. Потеря генератора более проста, просто количество единиц в массиве по сравнению с фактическим выходом дискриминатора.

Осталось только обучить модели. Мы создаем шум, затем пропускаем его через генератор для повышения частоты дискретизации. Затем мы вводим выходной сигнал генератора в дискриминатор и вычисляем потери. Мы используем это в градиентном спуске для изменения весов, а также в качестве оптимизатора. Надеюсь, это уменьшит потери.

def train_step(images):
noise = tf.random.normal([BATCH_SIZE, noise_dim])
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
#Make images:
generated_images = generator(noise, training=True)
#discriminator's output:
real_output = discriminator(images, training=True)
fake_output = discriminator(generated_images, training=True)
#loss:
gen_loss = generator_loss(fake_output)
disc_loss = discriminator_loss(real_output, fake_output)
gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))

Результаты

Небольшое замечание: я просмотрел всю записную книжку и все ее ячейки, прежде чем начать этот блог, и теперь, когда я писал этот раздел, это была эпоха 81/100. Каждая эпоха составляет примерно 25 секунд, и я создал 100, так что это 2500 секунд или 41 и 2/3 минуты работы. Как вы можете видеть, результаты не являются подробными и четкими лицами, но их можно легко идентифицировать как таковые.

Вывод

Понятно, что GAN способна генерировать много очень интересных вещей, например лица, полностью сгенерированные с нуля.

Однако потенциал еще больше. Увеличение разрешения и добавление синтетических эмоций для лиц — еще одна проблема, которую необходимо преодолеть. Достичь такого уровня детализации с помощью GAN будет непросто, но в когнитивной науке он может найти широкое применение.

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