Преобразование изображений из одного стиля в другой
Знаете ли вы, что вы можете сделать снимок лета и преобразовать его так, чтобы он выглядел как снимок, сделанный зимой? Для этого типа преобразования у нас есть набор различных алгоритмов, и CycleGAN — один из них, используемый для преобразования изображения в изображение.
Преобразование изображения в изображение — это класс задач компьютерного зрения и глубокого обучения, целью которого является изучение сопоставления между входным и выходным изображениями с использованием обучающего набора пар изображений.
Что такое CycleGAN?
Cycle Generative Adversarial Networks(CycleGAN) – это хорошо известный передовой алгоритм глубокого обучения. Он был разработан для решения проблемы перехода от изображения к изображению. CycleGAN является самым популярным алгоритмом и относится к набору алгоритмов, называемых генеративными моделями, и эти алгоритмы относятся к области техники обучения без учителя. Эта модель была впервые описана Джун-Ян Чжу в 2017 году.
Как мы знаем, для решения проблемы преобразования изображения в изображение алгоритм должен изучить сопоставление между входным изображением и выходным изображением с помощью обучающего набора парных изображений. Однако алгоритмы CycleGAN изучают сопоставление между входным и выходным изображением, не требуя набора парных входных и выходных изображений.
Архитектура CycleGAN
CycleGAN использует архитектуру модели Generative Adversarial Network (GAN). Он имеет две нейронные сети, называемые gгенератор и дискриминатор.
Работа генератора состоит в том, чтобы произвести выборку из желаемого дистрибутива, что означает создание реалистичных изображений, подобных ожидаемому. Работа дискриминатора состоит в том, чтобы отличать реальные изображения от поддельных, созданных генератором.
Обе сети (генератор и дискриминатор)обучаются одновременно, пока не наступает этап, когда генератор создает настолько реалистичные изображения, что дискриминатор не может определить, какое изображение реальное, а какое искусственно создано генератором. Этот этап называется равновесием. Как только достигается стадия равновесия, обучение модели автоматически прекращается.
Ниже у нас есть два сопоставления: одно G: X → Y, а второе F: Y → X. Генератор (G) пытается преобразовать (X) в выходные данные → (Y), и он проходит через Dy, чтобы проверить, являются ли изображения реальными или поддельными в соответствии с доменом (Y). Точно так же генератор (F) пытается преобразовать (Y) в выходные данные → (X), и он проходит через Dx, чтобы проверить, неотличимы ли изображения от домена (X).
Генератор (F и G) состоит из трех секций:
- Кодировщик — на самом первом этапе входное изображение передается в кодировщик. В этом разделе функция изображения извлекается из входного изображения с помощью сверток и сжимает представление изображения. Кодировщик имеет три свертки, которые уменьшают представление на 1/4 фактического размера изображения. Если входное изображение имеет размер 256, 256, 3, при вводе в кодировщик выход кодировщика будет 64, 64, 256.
- Transformer — для этого раздела ввод будет поступать от кодировщика после применения функции активации к выходу кодировщика. В зависимости от размера входа преобразователь содержит 6/9 остаточных блоков.
- Декодер — выход преобразователя является входом для декодера. Он использует два блока деконволюции дробных шагов, чтобы увеличить размер представления до исходного размера.
Функции потерь
Потеря согласованности цикла:как и в CycleGAN, нет парных данных (входное изображение и соответствующее выходное изображение) для обучения модели, поэтому мы не можем предсказать, что вход X и целевая пара Y имеет смысл во время обучения. Чтобы гарантировать, что модель изучает правильное сопоставление, была введена потеря согласованности цикла.
Генератор (G) берет входное изображение (изображение летнего сезона) из домена A и преобразует его в выходное изображение (изображение зимнего сезона) домена B, снова выходное изображение переведены обратно в домен A с помощью генератора (F). Таким образом, во время полного цикла разница между этими двумя изображениями называется потерей согласованности цикла, как показано на изображении ниже.
Потери со стороны противника:потери, рассчитанные как для наших отображений генераторов, так и для дискриминаторов, называются потерями со стороны противника и рассчитываются по приведенной ниже формуле.
Таким образом, общая потеря, рассчитанная как сумма потерь противника и потери согласованности цикла, равна окончательной формуле, приведенной ниже.
Выполнение
Давайте посмотрим, как реализовать CycleGAN
шаг за шагом. Во-первых, нам нужно установить необходимые библиотеки.
pip install tensorflow pip install matplotlib
Импортируйте указанные ниже библиотеки.
import tensorflow as tf import os import numpy as np import matplotlib.pyplot as plt import tensorflow_datasets as tfds import tensorflow_addons as tfa from tensorflow import keras from tensorflow.keras import layers
Следующим шагом является подготовка набора данных. Используя наборы данных TensorFlow, загрузите набор данных «лошадь-зебра» и определите размер изображения, размер буфера и размер пакета.
dataset, _ = tfds.load("cycle_gan/horse2zebra", with_info=True, as_supervised=True) train_horse, train_zebra = dataset["trainA"], dataset["trainB"] test_horse, test_zebra = dataset["testA"], dataset["testB"] buffer_size = 256 batch_size = 1 # Define image size. org_image_size=(286,286) # Size to be used during training. input_image_size = (256, 256, 3) # Gamma initializer for instance normalization. gamma_init = keras.initializers.RandomNormal(mean=0.0,stddev=0.02) # Weights initializer for the layer. kernel_init = keras.initializers.RandomNormal(mean=0.0,stddev=0.02)
Определены функции для нормализации изображения, а также для обработки и обучения изображения и тестовых изображений по отдельности.
def normalize_image(image): image = tf.cast(image, dtype=tf.float32) return (image/127.5) - 1.0 def preprocess_train_image(image, label): image = tf.image.random_flip_left_right(image) image = tf.image.resize(image, [*org_image_size]) image = tf.image.random_crop(image, size=[*input_image_size]) # Normalize the pixel values in the range [-1, 1] image = normalize_image(image) return image def preprocess_test_image(image, label): # used for resizing and normalization for the test images. image = tf.image.resize(image, [input_image_size[0], input_image_size[1]]) image = normalize_image(image) return image
Далее мы обработаем обучающие данные и тестовые данные, используя приведенный ниже код Python для лошади (домен A) и зебры (домен B).
train_horse = ( train_horse.map(preprocess_train_image, num_parallel_calls=autotune) .cache() .shuffle(buffer_size) .batch(batch_size) ) train_zebra = ( train_zebra.map(preprocess_train_image, num_parallel_calls=autotune) .cache() .shuffle(buffer_size) .batch(batch_size) ) # Test data processing test_horse = ( test_horse.map(preprocess_test_image, num_parallel_calls=autotune) .cache() .shuffle(buffer_size) .batch(batch_size) ) test_zebra = ( test_zebra.map(preprocess_test_image, num_parallel_calls=autotune) .cache() .shuffle(buffer_size) .batch(batch_size) )
Теперь мы определим функцию cycleGAN_resnet_generator()для создания модели длягенератора. Код Python для этого приведен ниже.
def cycleGAN_resnet_generator( filters=64, num_downsampling_blocks=2, num_residual_blocks=9, num_upsample_blocks=2, gamma_initializer=gamma_init, name=None, ): img_input = layers.Input(shape=input_image_size, name=name + "_img_input") x = Reflection_Padding2D(padding=(3, 3))(img_input) x = layers.Conv2D(filters, (7, 7), kernel_initializer=kernel_init, use_bias=False)( x ) x = tfa.layers.InstanceNormalization(gamma_initializer=gamma_initializer)(x) x = layers.Activation("relu")(x) # Downsampling for _ in range(num_downsampling_blocks): filters *= 2 x = downsample1(x, filters=filters, activation=layers.Activation("relu")) # Residual blocks for _ in range(num_residual_blocks): x = residual_block1(x, activation=layers.Activation("relu")) # Upsampling for _ in range(num_upsample_blocks): filters //= 2 x = upsample1(x, filters, activation=layers.Activation("relu")) # Final block x = Reflection_Padding2D(padding=(3, 3))(x) x = layers.Conv2D(3, (7, 7), padding="valid")(x) x = layers.Activation("tanh")(x) model = keras.models.Model(img_input, x, name=name) return model
Точно так же функция cycleGAN_distributor() определена для создания модели дискриминатора.
def cycleGAN_discriminator( filters=64,kernel_initializer=kernel_init,num_downsampling=3, name=None ): img_input = layers.Input(shape=input_image_size, name=name + "_img_input") x = layers.Conv2D( filters, (4,4), strides=(2, 2), padding="same", kernel_initializer=kernel_initializer, )(img_input) x = layers.LeakyReLU(0.2)(x) num_filters = filters for num_downsample_block in range(3): num_filters *= 2 if num_downsample_block < 2: x = downsample1( x, filters=num_filters, activation=layers.LeakyReLU(0.2), kernel_size=(4, 4), strides=(2, 2), ) else: x = downsample1( x, filters=num_filters, activation=layers.LeakyReLU(0.2), kernel_size=(4, 4), strides=(1, 1), ) x = layers.Conv2D( 1, (4, 4), strides=(1, 1), padding="same", kernel_initializer=kernel_initializer )(x) model = keras.models.Model(inputs=img_input, outputs=x, name=name) return model
Как и в CycleGAN, существуют два генератора (G и F) и два дискриминатора (X и Y), поэтому отдельные значения назначаются в соответствии с определением функции, написанным на предыдущем шаге.
# Get the generators Gen_G = get_resnet_generator(name = "generator_G") Gen_F = get_resnet_generator(name = "generator_F") # Get the discriminators Disc_X = get_discriminator(name = "discriminator_X") Disc_Y = get_discriminator(name = "discriminator_Y")
Построить модель
Теперь наша цель — построить модель CycleGAN. На этом этапе мы рассчитаем различия и потери между изображениями (входными и выходными) для генераторов и дискриминаторов. Для этого мы последовательно выполним следующие шаги.
- Пропустите входные изображения через генераторы и получите сгенерированные изображения.
- Передайте сгенерированные изображения обратно в генераторы, чтобы убедиться, что мы можем предсказать исходное изображение из сгенерированного изображения.
- Используя генераторы, выполните сопоставление идентичности реальных изображений.
- Передайте сгенерированные изображения из шага 1 в соответствующие дискриминаторы.
- Найдите общие потери генераторов = (противодействие + цикл + идентичность).
- Найдите потери дискриминаторов.
- Обновите веса генераторов.
- Обновите веса дискриминаторов.
- Верните потери в словарь.
class cycleGan(keras.Model): def __init__( self, generator_G, generator_F, discriminator_X, discriminator_Y, lambda_cycle=10.0, lambda_identity=0.5, ): super(CycleGan, self).__init__() self.gen_G = generator_G self.gen_F = generator_F self.disc_X = discriminator_X self.disc_Y = discriminator_Y self.lambda_cycle = lambda_cycle self.lambda_identity = lambda_identity def compile( self, gen_G_optimizer, gen_F_optimizer, disc_X_optimizer, disc_Y_optimizer, gen_loss_fn, disc_loss_fn, ): super(CycleGan, self).compile() self.gen_G_optimizer = gen_G_optimizer self.gen_F_optimizer = gen_F_optimizer self.disc_X_optimizer = disc_X_optimizer self.disc_Y_optimizer = disc_Y_optimizer self.generator_loss_function = gen_loss_fn self.discriminator_loss_function = disc_loss_fn self.cycle_loss_fn = keras.losses.MeanAbsoluteError() self.identity_loss_fn = keras.losses.MeanAbsoluteError() def train_step(self, batch_data): real_x, real_y = batch_data with tf.GradientTape(persistent=True) as tape: # Horse to fake new zebra fake_y = self.gen_G(real_x, training=True) # Zebra to fake new horse -> y2x fake_x = self.gen_F(real_y, training=True) # Cycle (Horse to new fake zebra to fake horse): x -> y -> x cycled_x = self.gen_F(fake_y, training=True) # Cycle (Zebra to fake horse to fake zebra) y -> x -> y cycled_y = self.gen_G(fake_x, training=True) # Identity mapping same_x = self.gen_F(real_x, training=True) same_y = self.gen_G(real_y, training=True) # Discriminator output Disc_real_x = self.disc_X(real_x, training=True) Disc_fake_x = self.disc_X(fake_x, training=True) disc_real_y = self.disc_Y(real_y, training=True) Disc_fake_y = self.Disc_Y(fake_y, training=True) # Generator adverserial loss gen_G_loss = self.generator_loss_function(Disc_fake_y) gen_F_loss = self.generator_loss_function(Disc_fake_x) # Generator cycle loss cycle_loss_G = self.cycle_loss_fn(real_y, cycled_y) * self.lambda_cycle cycle_loss_F = self.cycle_loss_fn(real_x, cycled_x) * self.lambda_cycle # Generator identity loss id_loss_G = ( self.identity_loss_fn(real_y, same_y) * self.lambda_cycle * self.lambda_identity ) id_loss_F = ( self.identity_loss_fn(real_x, same_x) * self.lambda_cycle * self.lambda_identity ) # Total generator loss total_loss_G = gen_G_loss + cycle_loss_G + id_loss_G total_loss_F = gen_F_loss + cycle_loss_F + id_loss_F # Discriminator loss Disc_X_loss = self.discriminator_loss_function(Disc_real_x, Disc_fake_x) Disc_Y_loss = self.discriminator_loss_function(Disc_real_y, Disc_fake_y) # find the gradients for the generators grads_G = tape.gradient(total_loss_G, self.gen_G.trainable_variables) grads_F = tape.gradient(total_loss_F, self.gen_F.trainable_variables) # Find the gradients for the discriminators disc_X_grads = tape.gradient(disc_X_loss, self.disc_X.trainable_variables) disc_Y_grads = tape.gradient(disc_Y_loss, self.disc_Y.trainable_variables) # Update the weights of the generators self.gen_G_optimizer.apply_gradients( zip(grads_G, self.gen_G.trainable_variables) ) self.gen_F_optimizer.apply_gradients( zip(grads_F, self.gen_F.trainable_variables) ) # Update the weights of the discriminators self.disc_X_optimizer.apply_gradients( zip(disc_X_grads, self.disc_X.trainable_variables) ) self.disc_Y_optimizer.apply_gradients( zip(disc_Y_grads, self.disc_Y.trainable_variables) ) return { "G_loss" : total_loss_G, "F_loss" : total_loss_F, "D_X_loss": disc_X_loss, "D_Y_loss": disc_Y_loss, }
Определены функции для расчета потерь от генераторов и дискриминаторов с помощью keras.losses.MeanSquaredError() .
Мы компилируем модель после генерации, и код Python написан ниже.
# Loss function for evaluating loss adv_loss_fn = keras.losses.MeanSquaredError() # Here we are defining the loss function for the generators def generator_loss_function(fake): fake_loss = adv_loss_fn(tf.ones_like(fake), fake) return fake_loss # Define the loss function for the discriminators def discriminator_loss_function(real, fake): real_loss = adv_loss_fn(tf.ones_like(real), real) fake_loss = adv_loss_fn(tf.zeros_like(fake), fake) return (real_loss + fake_loss) * 0.5 # Model Creation steps cycle_gan_model = CycleGan( generator_G=gen_G, generator_F=gen_F, discriminator_X=disc_X, discriminator_Y=disc_Y ) # Next, will Compile the model cycle_gan_model.compile( gen_G_optimizer=keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5), gen_F_optimizer=keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5), disc_X_optimizer=keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5), disc_Y_optimizer=keras.optimizers.Adam(learning_rate=2e-4, beta_1=0.5), gen_loss_fn=generator_loss_function, disc_loss_fn=discriminator_loss_function, ) # Callbacks plotter = GANMonitor() checkpoint_filepath = "./model_checkpoints/cyclegan_checkpoints.{epoch:03d}" model_checkpoint_callback = keras.callbacks.ModelCheckpoint( filepath=checkpoint_filepath ) # Here we will train the model for just one epoch. cycle_gan_model.fit( tf.data.Dataset.zip((train_horses,train_zebras)), epochs =1, callbacks=[plotter, model_checkpoint_callback], )
Прогноз — вывод
Это последний этап, на котором мы найдем выходные данные в соответствии с моделью, созданной путем предоставления входных данных.
weight_files = "./saved_checkpoints/cyclegan_checkpoints.080"
cycle_gan_model.load_weights(weight_files).expect_partial()
_, ax = plt.subplots(4,2, figsize=(15,26))
for i, img in enumerate(test_horses.take(4)):
prediction = cycle_gan_model.gen_G(img, training=False)[0].numpy()
prediction = (prediction * 127.5 + 127.5).astype(np.uint8)
img = (img[0] * 127.5+127.5).numpy().astype(np.uint8)
ax[i,0].imshow(img)
ax[i,1].imshow(prediction)
ax[i,0].set_title("Input image")
ax[i,1].set_title("Translated image")
ax[i,0].axis("off")
ax[i,1].axis("off")
prediction = keras.preprocessing.image.array_to_img(prediction)
prediction.save("predicted_img_{i}.png".format(i=i))
plt.tight_layout()
plt.show()
Из-за нехватки места я показываю только две пары ввода-вывода, но вы можете легко понять конвертированные изображения лошади в изображения зебры.
Вывод
Мы рассмотрели CycleGAN и его архитектуру. В настоящее время концепции модели CycleGAN используются во многих приложениях — одним из них является FaceApp.
Итак, куда идти отсюда? Мы можем больше поэкспериментировать с условной частью модели, чтобы посмотреть, сможем ли мы добиться таких вещей, как выполнение нескольких задач одновременно, и мы увидим, как генератор будет вести себя с другим условным входным изображением. Есть много возможностей для экспериментов и улучшений, над которыми я буду работать и призываю вас делать то же самое!
Примечание редактора. Heartbeat — это интернет-издание и сообщество, созданное участниками и посвященное предоставлению лучших образовательных ресурсов для специалистов по науке о данных, машинному обучению и глубокому обучению. Мы стремимся поддерживать и вдохновлять разработчиков и инженеров из всех слоев общества.
Независимая от редакции, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по данным и командам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим нашим авторам и не продаем рекламу.
Если вы хотите внести свой вклад, перейдите к нашему призыву к участию. Вы также можете подписаться на получение наших еженедельных информационных бюллетеней (Еженедельник глубокого обучения и Информационный бюллетень Comet), присоединиться к нам в Slack и следить за Comet в Twitter и LinkedIn для получения ресурсов, событий и многое другое, что поможет вам быстрее создавать лучшие модели машинного обучения.