循环GANs的非配对图像到图像的翻译

244 阅读9分钟

Unpaired Image to Image Translations with Cycle GANs

照片:Tom Copus/Unsplash

Unpaired Image to Image Translations with Cycle GANs

几十年前,深度学习被引入时没有起飞的主要原因之一是缺乏数据。神经网络就其本质而言,需要巨大的处理能力和大数据才能真正在实践中得到利用。在当今时代,随着设备的现代化,存储大量的数据不是问题。也可以用计算机视觉或自然语言处理的现代工具来创建和完善可用的数据。大多数深度学习模型利用成对的例子,其中源图像与它的目标图像直接相连。然而,那里出现了一个重要的问题。什么类型的深度学习对于解决彼此之间几乎没有联系的包含数据的任务来说是最佳的?

在我们之前的文章中,我们一直在探索条件型GANs及其在成功训练后相对轻松地完成图像到图像翻译的复杂任务的能力。我们已经涵盖了条件式GANs(CGANs)和这些条件式GANs在pix2pix GANs中的一个变种。为了快速回顾我们之前关于pix2pix的GAN文章,我们专注于执行卫星图像到地图的图像到图像的翻译。在这篇文章中,我们将专注于循环一致对抗网络(Cycle GANs)中条件GANs的另一种变体,并开发一个非配对图像到图像的翻译项目。

可以利用Paperspace Gradient平台来运行以下项目,方法是使用以下链接的repo作为 "工作区URL "来创建Paperspace Gradient笔记本。 这个字段可以通过切换笔记本创建页面上的高级选项按钮找到。

介绍

当我们有特定的条件和适当的数据元素配对,把一张图片和它各自的对应物联系起来时,训练这样的条件性GANs就比较容易。Pix2Pix GANs就是这样一个例子,我们用成对的图像工作。成对的图像是指源图像与相应的目标图像相联系的图像。在成对的图像数据集中,我们对源图像和目标图像都有明确的映射,并相应提供。然而,这样的数据在现实世界中很难获得。

另一方面,非配对数据则相对更容易获得,而且可以找到大量的数据。在非配对数据集中,源图像和其相应的目标图像并不直接提供。解决与未配对数据集相关的项目或任务的主要方法之一是利用循环一致对抗网络(Cycle GANs)。循环GANs为用户提供了一种在大多数情况下取得令人信服的结果的有效方法。在这篇研究论文中,详细描述了该网络的大部分主要目标和成就。

循环GAN网络能够从未配对的例子中学习从图像源XXYY的映射,这样产生的图像是高质量的。在这篇研究论文中还引入了一个额外的循环损失,以确认从生成的图像的反映射再次重现了源图像。让我们在本文接下来的章节中进一步了解工作程序和实施细节。


了解周期GANs

Unpaired Image to Image Translations with Cycle GANs

图像源

在本节中,我们将了解循环GANs的详细工作解释。在介绍中,我们轻轻地承认,循环GANs的应用主要是针对非配对的图像到图像的翻译,与其他条件性GANs相反,如pix2pix GANs。让我们试着了解这些网络的工作程序和基本概念,并对生成器和鉴别器的结构有一个清晰的认识。

在上面的图片中,让我们考虑第一个子部分,其中有生成器GGFF以及判别器DxDxDyDy。X代表输入图像,而代表输入图像,而Y代表生成的图像。与大多数GAN网络不同的是,它利用一个生成器和鉴别器,循环GAN网络利用两个生成器和鉴别器。生成器代表生成的图像。与大多数GAN网络不同的是,它利用一个生成器和鉴别器,循环GAN网络利用两个生成器和鉴别器。生成器G在输入的在输入的X上工作,生成一个图像上工作,生成一个图像Y。鉴别器。鉴别器Dy可以区分生成的图像是真的还是假的。同样,生成器可以区分生成的图像是真的还是假的。同样,生成器F在考虑输入在考虑输入Y的情况下生成图像的情况下生成图像X。鉴别器。鉴别器Dx$区分这个生成的图像是真的还是假的。

除了有双重生成器和鉴别器的设置外,我们还有一个循环一致性损失,以帮助模型捕捉更多的直观理解。如果模型是在一个源图像上训练产生的,那么循环一致性损失可以确保当生成的图像再次被翻译回原域时,我们应该能够再次近似地检索到原始源图像。

在上图中,我们可以注意到前向循环一致性损失和后向循环一致性损失在它们各自的子图中。下面是周期GAN网络的所有损失函数组合的最终方程式。在这个项目中,我们还使用了一个额外的身份损失,这在大多数情况下可能并不需要。

L(G,F,DX,DY)=LGAN(G,DY,X,Y)+LGAN(F,DX,Y,X)+λLcyc(G,F)L(G, F, DX, DY ) = LGAN(G, DY , X, Y ) + LGAN(F, DX, Y, X) + λLcyc(G, F)

现在我们已经了解了这些循环GAN网络的工作程序,我们也可以简单地讨论一下生成器和鉴别器网络的实现背后的细节。生成器架构由三个卷积层组成,具有不同的过滤器、内核大小和步长。在这三个卷积层之后,我们还有剩余块。该网络可以包含六个或九个残差块。

在残余块的最后,我们利用几个上采样层(或卷积转置层),最后的卷积层产生发生器所需的图像大小。在鉴别器架构中,我们有一个简单的70 x 70 Patch GAN网络,它包含四个卷积层,过滤器的大小不断增加。在这些网络中,与Leaky ReLU激活函数一起,实例归一化优于批量归一化。当我们从头开始构建这些模型时,我们将进一步讨论更多关于架构的细节。


使用循环GANS开发非配对图像到图像的翻译。

在文章的这一部分,我们将重点讨论在Cycle GANs的帮助下开发非配对图像到图像的翻译项目。现在我们对这些循环一致对抗网络的基本工作程序有了简单的了解,我们可以建立整体架构来处理和计算所需的任务。我们将利用TensorFlow和Keras深度学习框架来构建这些网络。如果观众对这些库不熟悉,我建议查看我以前的几篇文章,以获得对这些主题的更多熟悉。你可以查看下面的文章来了解更多关于TensorFlow和Keras文章。一旦完成,我们就可以开始导入基本库了。

导入必要的库

如前所述,与之前的pix2pix GAN架构相比,一个主要的变化是使用实例归一化层而不是批量归一化层。由于没有办法通过现有的Keras API直接调用实例归一化层,我们将着手安装一个额外的需求。我们将安装Keras-Contrib资源库,它将使我们能够直接利用这些必要的层,而不需要经历太多的麻烦。

pip install git+https://www.github.com/keras-team/keras-contrib.git

一旦我们完成了项目的前提要求的安装,我们就可以着手导入所有的基本库,我们将利用这些库来构建循环GAN架构,并相应地训练模型以获得所需的结果。如前所述,我们将使用TensorFlow和Keras深度学习框架来构建网络。我们将使用功能性API模型类型,而不是顺序模型,以便对网络设计有更高的整体控制。我们还将使用TensorFlow数据集库中的数据集。其他必要的导入包括用于可视化的matplotlib和用于处理本地操作系统相关任务的OS。下面是所有需要导入的列表。

import tensorflow as tf
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.initializers import RandomNormal
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, LeakyReLU, Conv2DTranspose
from tensorflow.keras.layers import Activation, Concatenate, BatchNormalization
from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization
from tensorflow.keras.utils import plot_model
import tensorflow_datasets as tfds

import matplotlib.pyplot as plt
from IPython.display import clear_output
import time
import os

AUTOTUNE = tf.data.AUTOTUNE

获取和准备数据集。

在这一步,我们将获得数据集,我们将对其进行图像翻译。我们利用TensorFlow数据集库,通过它我们可以检索到所有的相关信息。该数据集包含马和斑马的图像。为了获得类似的循环GAN项目的数据集,我建议查看以下链接。下面是将数据集加载到各自的训练变量和测试变量的代码片段。

dataset, metadata = tfds.load('cycle_gan/horse2zebra',
                              with_info=True, as_supervised=True)

train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']

一旦我们获取了数据集,我们就可以继续定义一些基本参数,我们将利用这些参数来准备数据集。

BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256

在下一个代码块中,我们将为数据集的准备工作定义一些基本功能。我们将对数据集进行规范化处理,以避免额外的内存使用,并以相对较少的资源来解决这个任务。我们还将按照研究论文中的建议,对现有的数据进行抖动和镜像处理,以避免过度拟合。这样的扩增技术通常是相当有用的。在这一步中,我们要将图像的大小调整为286 x 286,然后裁剪回所需的256 x 256大小,并将图像从左到右水平翻转。下面是执行以下动作的代码块。

def random_crop(image):
    cropped_image = tf.image.random_crop(
      image, size=[IMG_HEIGHT, IMG_WIDTH, 3])
    
    return cropped_image

# normalizing the images to [-1, 1]
def normalize(image):
    image = tf.cast(image, tf.float32)
    image = (image / 127.5) - 1
    return image

def random_jitter(image):
    # resizing to 286 x 286 x 3
    image = tf.image.resize(image, [286, 286],
                          method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

    # randomly cropping to 256 x 256 x 3
    image = random_crop(image)

    # random mirroring
    image = tf.image.random_flip_left_right(image)

    return image

def preprocess_image_train(image, label):
    image = random_jitter(image)
    image = normalize(image)
    return image

def preprocess_image_test(image, label):
    image = normalize(image)
    return image

最后,我们将把所有这些数据元素计算成一个最终的数据集。我们将用随机洗牌和预先定义的批量大小来映射数据,通过它所有的组件都可以访问。我们将为马和斑马图像的所有训练元素和测试元素定义数据集,如以下代码片段所示。

train_horses = train_horses.cache().map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

train_zebras = train_zebras.cache().map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

test_horses = test_horses.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

test_zebras = test_zebras.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)
    
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))

一旦我们完成了获得和准备数据集的所有必要步骤,我们就可以继续创建鉴别器和生成器网络,以建立整个循环GAN架构。

定义鉴别器架构

对于鉴别器架构,我们有四个卷积块,定义如下 - C64C128C256C512C64-C128-C256-C512。使用α(斜率)值为0.2的Leaky ReLU激活函数。除了第一个卷积块之外,其他所有块都在卷积层之后使用了实例归一化。步幅和内核大小如下面的代码片段所示。我们最后将用均方误差损失函数和亚当优化器来编译模型,以完成Patch GAN判别器类型的网络。

# define the discriminator model
def define_discriminator(image_shape):
    # weight initialization
    init = RandomNormal(stddev=0.02)
    # source image input
    in_image = Input(shape=image_shape)
    # C64
    d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
    d = LeakyReLU(alpha=0.2)(d)
    # C128
    d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = InstanceNormalization(axis=-1)(d)
    d = LeakyReLU(alpha=0.2)(d)
    # C256
    d = Conv2D(256, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = InstanceNormalization(axis=-1)(d)
    d = LeakyReLU(alpha=0.2)(d)
    # C512
    d = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = InstanceNormalization(axis=-1)(d)
    d = LeakyReLU(alpha=0.2)(d)
    # second last output layer
    d = Conv2D(512, (4,4), padding='same', kernel_initializer=init)(d)
    d = InstanceNormalization(axis=-1)(d)
    d = LeakyReLU(alpha=0.2)(d)
    # patch output
    patch_out = Conv2D(1, (4,4), padding='same', kernel_initializer=init)(d)
    # define model
    model = Model(in_image, patch_out)
    # compile model
    model.compile(loss='mse', optimizer=Adam(learning_rate=0.0002, beta_1=0.5), loss_weights=[0.5])
    return model

# define image shape
image_shape = (256,256,3)

# create the model
model = define_discriminator(image_shape)

# summarize the model
model.summary()

定义生成器架构

生成器架构包括一个卷积块,有64个滤波器,内核大小为7×7,跨度为2,然后是两个3×3内核大小的卷积层,跨度为1,分别有128和256个滤波器。然后我们将定义残差块,它只不过是一堆卷积层。这个残差块之后是由卷积二维转置层定义的几个上采样层。最后的卷积层包含一个7 x 7的内核大小,跨度为1和3个过滤器。

他们对9个残余块的发生器架构定义如下。c7s164,d128,d256,R256,R256,R256,R256,R256,R256,R256,u128,u64,c7s13c7s1-64, d128, d256, R256, R256, R256, R256, R256, R256, R256, u128, u64, c7s1-3

# generator a resnet block
def resnet_block(n_filters, input_layer):
    # weight initialization
    init = RandomNormal(stddev=0.02)
    # first layer convolutional layer
    g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(input_layer)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # second convolutional layer
    g = Conv2D(n_filters, (3,3), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    # concatenate merge channel-wise with input layer
    g = Concatenate()([g, input_layer])
    return g

# define the standalone generator model
def define_generator(image_shape=(256, 256, 3), n_resnet=9):
    # weight initialization
    init = RandomNormal(stddev=0.02)
    # image input
    in_image = Input(shape=image_shape)
    # c7s1-64
    g = Conv2D(64, (7,7), padding='same', kernel_initializer=init)(in_image)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # d128
    g = Conv2D(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # d256
    g = Conv2D(256, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # R256
    for _ in range(n_resnet):
        g = resnet_block(256, g)
    # u128
    g = Conv2DTranspose(128, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # u64
    g = Conv2DTranspose(64, (3,3), strides=(2,2), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    g = Activation('relu')(g)
    # c7s1-3
    g = Conv2D(3, (7,7), padding='same', kernel_initializer=init)(g)
    g = InstanceNormalization(axis=-1)(g)
    out_image = Activation('tanh')(g)
    # define model
    model = Model(in_image, out_image)
    return model

# create the model
model = define_generator()
# summarize the model
model.summary()

定义损失函数和检查点

在接下来的代码片段中,我们将探索不同类型的损失函数,我们将利用循环GAN架构。我们将定义生成器损失、判别器损失、周期一致性损失和身份损失。如前所述,循环一致性损失的原因是为了保持源图像和再现图像之间的近似关系。最后,我们将为生成器和判别器网络定义亚当优化器,如下面的代码块所示。

LAMBDA = 10

loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)

def discriminator_loss(real, generated):
    real_loss = loss_obj(tf.ones_like(real), real)

    generated_loss = loss_obj(tf.zeros_like(generated), generated)

    total_disc_loss = real_loss + generated_loss

    return total_disc_loss * 0.5

def generator_loss(generated):
    return loss_obj(tf.ones_like(generated), generated)

def calc_cycle_loss(real_image, cycled_image):
    loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))

    return LAMBDA * loss1

def identity_loss(real_image, same_image):
    loss = tf.reduce_mean(tf.abs(real_image - same_image))
    return LAMBDA * 0.5 * loss

generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

一旦我们完成了损失函数和优化器的定义,我们还将定义一个检查点系统,在这里我们将存储所需的检查点,最新的结果被保存下来。我们这样做可以让我们在需要时重新加载和重新训练权重。

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(generator_g=generator_g,
                           generator_f=generator_f,
                           discriminator_x=discriminator_x,
                           discriminator_y=discriminator_y,
                           generator_g_optimizer=generator_g_optimizer,
                           generator_f_optimizer=generator_f_optimizer,
                           discriminator_x_optimizer=discriminator_x_optimizer,
                           discriminator_y_optimizer=discriminator_y_optimizer)

ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)

# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
    ckpt.restore(ckpt_manager.latest_checkpoint)
    print ('Latest checkpoint restored!!')

定义最终的训练函数

在最后一步,我们将定义训练函数来训练我们的模型,并按要求生成所需的图像。首先,让我们设置我们计划训练模型的历时数,并创建一个函数来创建各自的图,以可视化输入和预测的图像。请注意,在这次训练中,我在二十个历时后使用了键盘中断,但浏览者可以训练更长的时间以达到更好的效果。

EPOCHS = 50

def generate_images(model, test_input):
    prediction = model(test_input)

    plt.figure(figsize=(12, 12))

    display_list = [test_input[0], prediction[0]]
    title = ['Input Image', 'Predicted Image']

    for i in range(2):
        plt.subplot(1, 2, i+1)
        plt.title(title[i])
        # getting the pixel values between [0, 1] to plot it.
        plt.imshow(display_list[i] * 0.5 + 0.5)
        plt.axis('off')
    plt.show()

在训练步骤函数中,我们将调用tf.函数和Gradient tape,以便更快地训练计算反向传播的权重。训练方法与我们之前构建的GAN架构相似,不同的是我们将在此方法中训练两个生成器和判别器,并根据需要评估循环一致性损失。下面是循环GAN架构的完整训练程序的代码块。

@tf.function
def train_step(real_x, real_y):
  # persistent is set to True because the tape is used more than
  # once to calculate the gradients.
    with tf.GradientTape(persistent=True) as tape:
        # Generator G translates X -> Y
        # Generator F translates Y -> X.

        fake_y = generator_g(real_x, training=True)
        cycled_x = generator_f(fake_y, training=True)

        fake_x = generator_f(real_y, training=True)
        cycled_y = generator_g(fake_x, training=True)

        # same_x and same_y are used for identity loss.
        same_x = generator_f(real_x, training=True)
        same_y = generator_g(real_y, training=True)

        disc_real_x = discriminator_x(real_x, training=True)
        disc_real_y = discriminator_y(real_y, training=True)

        disc_fake_x = discriminator_x(fake_x, training=True)
        disc_fake_y = discriminator_y(fake_y, training=True)

        # calculate the loss
        gen_g_loss = generator_loss(disc_fake_y)
        gen_f_loss = generator_loss(disc_fake_x)

        total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)

        # Total generator loss = adversarial loss + cycle loss
        total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
        total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)

        disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
        disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)

    # Calculate the gradients for generator and discriminator
    generator_g_gradients = tape.gradient(total_gen_g_loss, 
                                        generator_g.trainable_variables)
    generator_f_gradients = tape.gradient(total_gen_f_loss, 
                                        generator_f.trainable_variables)

    discriminator_x_gradients = tape.gradient(disc_x_loss, 
                                            discriminator_x.trainable_variables)
    discriminator_y_gradients = tape.gradient(disc_y_loss, 
                                            discriminator_y.trainable_variables)

    # Apply the gradients to the optimizer
    generator_g_optimizer.apply_gradients(zip(generator_g_gradients, 
                                            generator_g.trainable_variables))

    generator_f_optimizer.apply_gradients(zip(generator_f_gradients, 
                                            generator_f.trainable_variables))

    discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
                                                discriminator_x.trainable_variables))

    discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
                                                discriminator_y.trainable_variables))

最后,我们可以开始训练过程,进行定义的epochs数量。我们将循环浏览我们的训练数据并相应地训练模型。在每个历时结束时,我们可以生成结果图像,以注意到模型的进展情况。我们还可以保存检查点,如下面的代码片断所示。

for epoch in range(EPOCHS):
    start = time.time()

    n = 0
    for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
        train_step(image_x, image_y)
        if n % 10 == 0:
            print ('.', end='')
        n += 1

    clear_output(wait=True)
    # Using a consistent image (sample_horse) so that the progress of the model
    # is clearly visible.
    generate_images(generator_g, sample_horse)

    if (epoch + 1) % 5 == 0:
        ckpt_save_path = ckpt_manager.save()
        print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))

    print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
                                                      time.time()-start))
Saving checkpoint for epoch 20 at ./checkpoints/train/ckpt-4

在循环GANs的帮助下,我们可以注意到用训练过的架构获得的整体结果是相当引人注目的。我们能够在明显可解释的程度上为非配对图像翻译任务产生预期的结果。虽然这个网络有一些局限性,包括背景颜色和纹理细节的损失,但我们仍然能够检索出特定任务的大约大部分必要信息。我建议尝试众多的参数、变化和微小的结构变化,以达到更理想的效果。

这段代码的主要部分是从TensorFlow的官方网站上考虑的,可以从这个链接查看。然而,他们利用pix-2-pix模型的一个变体来训练循环GAN网络,以达到简单化,利用类似U-Net的生成器网络和相应的判别器网络。在这篇文章中,我们只专注于从头开始重建研究论文,并将ResNet架构用于生成器,将稍加修改的Patch GAN架构用于鉴别器。我建议查看下面的参考链接,以了解更多关于循环GAN发生器和判别器实现的详细指南。


结语

Unpaired Image to Image Translations with Cycle GANs

照片:Chris Stenger/Unsplash

在自然界中,对于复杂的问题,很难获得大量的成对的例子数据集。创建这样的配对数据集,将源图像与各自的目标图像联系起来,往往是昂贵和耗时的。然而,大量未配对的图像对图像的翻译实例可以通过互联网获得。本文讨论的马和斑马数据集就是这样一个例子,其中所需的输出没有明确的定义。大多数现代GAN可以解决与图像到图像翻译的配对例子有关的任务和项目。但循环GAN的条件GAN有能力在未配对的数据集上实现理想的输出。

在这篇文章中,我们探索了循环一致对抗网络(Cycle GANs)中条件GANs的另一种变体。我们了解到这些GANs有多强大,以及它们即使在没有配对例子的情况下学习图像到图像的翻译的能力。我们对这些循环GANs的一些基本概念进行了简要介绍,同时对其网络结构的一些核心概念进行了详细分解。最后,我们以 "使用循环GANS的无配对图像到图像翻译 "项目的从无到有的发展来总结我们的概念理解。

在未来的文章中,我们将介绍更多类型的GANs,如ProGAN、StyleGAN等等。我们还将用深度学习来实验一些音频任务,并学习从头开始构建神经网络。在那之前,请继续学习和探索