ProGAN: 逐步增长的生成对抗网络

258 阅读19分钟

人类的学习过程是一个渐进的曲线。作为婴儿,我们在几个月的时间里从坐、爬、站、走、跑等方面慢慢进步。大多数对概念的理解也是从初学者开始逐渐进步,并继续学习到高级/硕士水平。即使是语言的学习也是一个循序渐进的过程,从学习字母表开始,到理解单词,最后发展到形成句子的能力。这些例子提供了人类如何理解大多数基本概念的要领。我们慢慢地适应和学习新的主题,但这样的场景在机器和深度学习模型的情况下会如何运作?

在之前的博客中,我们已经研究了几种不同类型的生成式对抗网络,它们都有自己独特的方法来获得特定的目标。之前的一些作品包括循环GANspix-2-pix GANsSRGANs和其他多个生成式网络,它们都有自己独特的特征。然而,在这篇文章中,我们将专注于一个名为Progressive Growing of GANs的生成式对抗网络,它以大多数人类的方式学习模式,从最低级别开始,然后进行更高层次的理解。本文提供的代码可以有效地在Paperspace Gradient平台上运行,利用其大规模、高质量的资源来达到预期的效果。

引言

在ProGANs之前建立的大多数生成网络都使用了独特的技术,大多涉及损失函数的修改以获得预期的结果。这些结构的生成器和判别器中的各层总是在一次训练中完成的。这时的大多数生成网络都是在其他基本特征和参数上进行改进,以提高结果,而没有真正涉及渐进式增长。然而,随着渐进式生长生成式对抗网络的引入,训练程序的重点是逐步增长网络,一次一个层。

对于这种渐进式增长的训练程序,直观的想法是经常人为地将训练图像缩小,缩减到最小的像素尺寸。一旦我们有了最低分辨率的图像,我们就可以开始随时间推移而获得稳定的训练程序。在这篇文章中,我们将进一步详细探讨ProGANs。在接下来的部分,我们将学习大部分的要求,以获得对ProGANs究竟如何工作的概念性理解。然后继续从头开始构建网络,以生成面部结构。不多说了,让我们深入了解这些创造性的网络。


了解ProGANs

ProGAN网络的主要思想是建立在层之上,从最低到最高开始。在下面的研究论文中,作者描述了生成器和判别器逐步学习的方法。如上图所示,我们从一个4 x 4的低分辨率图像开始,然后开始添加额外的微调和更高的参数变量,以达到更有效的结果。网络首先学习并理解4 x 4图像的工作。一旦完成,我们继续教它8 x 8的图像,16 x 16的分辨率,以此类推。上述例子中使用的最高分辨率是1024 x 1024。

这种循序渐进的训练方法使模型在整个训练过程中取得了前所未有的结果,整体上具有更高的稳定性。我们已经了解到,这个革命性的网络的主要想法之一是利用渐进式增长技术。我们之前还注意到,它不像其他架构那样对这些网络的损失函数有太多的纠缠。在这个实验中使用了一个默认的Wasserstein损失,但也可以利用其他类似的损失函数,如最小二乘法损失。观众可以从我以前的一篇关于Wasserstein生成式对抗网络(WGAN)的文章中了解更多关于这种损失的信息,请点击这个链接

除了渐进式生长网络的概念外,本文还介绍了其他一些重要的主题,即最小批量标准偏差、新层中的消退、像素正常化和均衡的学习率。我们将在本节中进一步探讨和理解这些概念,然后再进行它们的实施。

小批量标准偏差鼓励生成式网络在生成的图像中创造更多的变化,因为只考虑小批量。由于只考虑迷你批次,鉴别器更容易适应区分图像的真假,迫使生成器生成更多种类的图像。这个简单的技术解决了生成网络的一个主要问题,即生成的图像与各自的训练数据相比,往往变化较少。

研究论文中讨论的另一个重要概念是在新层中引入渐变。当从一个阶段过渡到另一个阶段时,即从较低的分辨率切换到下一个较高的分辨率时,新层会被平滑地淡化。这可以防止以前的层在加入新层时突然受到 "冲击"。参数alphaalpha用于控制淡化。这个参数是在多次训练迭代中线性插值的。如上图所示,最后的公式可以写成如下。

(1alpha)timesUpsampled,Layer+(alpha)timesOutput,Layer(1-\\alpha) times Upsampled \\, Layer + (\\alpha) \\times Output \\, Layer

在这一节中,我们将简要介绍的最后两个概念是均衡学习率和像素标准化。有了均衡的学习率,我们就可以对每层的权重进行相应的调整。其表述类似于凯明初始化或何氏初始化。但是,均衡化学习率不是把它作为一个单一的初始化器,而是在每个前向通道中使用。最后,像素归一化被用来代替批量归一化,因为人们注意到内部协变量偏移的问题在GANs中并不那么突出。像素归一化将每个像素中的特征向量归一到单位长度。在了解了这些基本概念后,我们可以着手构建用于生成面部图像的ProGAN网络架构。


从头开始构建ProGAN架构网络

在本节中,我们将涵盖从头构建渐进式生成对抗网络所需的大部分必要元素。我们将致力于用这些网络架构生成面部图像。完成这个编码构建的主要要求是一个用于训练的体面的GPU(或Paperspace梯度平台)和一些关于TensorFlow和Keras深度学习框架的基本知识。如果你对这两个库不熟悉,我建议你看看TensorFlow的这个链接和Keras的以下链接。让我们从导入必要的库开始。

导入必要的库

在第一步,我们将导入有效计算ProGAN网络所需的所有基本库。我们将导入TensorFlow和Keras深度学习框架,以建立最佳的鉴别器和生成器网络。NumPy库将被用于大多数需要执行的数学操作。我们还将利用一些计算机视觉库来处理相应的图像。此外,mtcnn库可以通过简单的pip安装命令来安装。下面是代表这个项目所需的所有库的代码片段:

from math import sqrt
from numpy import load, asarray, zeros, ones, savez_compressed
from numpy.random import randn, randint
from skimage.transform import resize
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Input, Dense, Flatten, Reshape, Conv2D
from tensorflow.keras.layers import UpSampling2D, AveragePooling2D, LeakyReLU, Layer, Add
from keras.constraints import max_norm
from keras.initializers import RandomNormal
import mtcnn
from mtcnn.mtcnn import MTCNN
from keras import backend
from matplotlib import pyplot
import cv2
import os
from os import listdir
from PIL import Image
import cv2

对数据进行预处理

本项目的数据集可以从以下网站下载。CelebFaces Attributes(CelebA)数据集是与面部检测和识别有关的任务中比较流行的数据集之一。在这篇文章中,我们将主要使用这个数据,在我们的ProGAN网络的帮助下生成新的独特面孔。我们将在接下来的代码块中定义的一些基本功能将帮助我们以更合适的方式处理这个项目。首先,我们将定义一个函数来加载图像,将其转换为RGB图像,并以numpy数组的形式存储。

在接下来的几个函数中,我们将利用预先训练好的多任务级联卷积神经网络(MTCNN),它被认为是用于人脸检测的最先进的准确性深度学习模型。使用这个模型的主要目的是确保我们只考虑这个名人数据集中的人脸,而忽略一些不必要的背景特征。因此,在将图像调整到所需大小之前,我们将使用之前安装在本地系统上的这个MTCnn库进行面部检测和提取:

# Loading the image file
def load_image(filename):
    image = Image.open(filename)
    image = image.convert('RGB')
    pixels = asarray(image)
    return pixels
    
# extract the face from a loaded image and resize
def extract_face(model, pixels, required_size=(128, 128)):
    # detect face in the image
    faces = model.detect_faces(pixels)
    if len(faces) == 0:
        return None
    
    # extract details of the face
    x1, y1, width, height = faces[0]['box']
    x1, y1 = abs(x1), abs(y1)
    
    x2, y2 = x1 + width, y1 + height
    face_pixels = pixels[y1:y2, x1:x2]
    image = Image.fromarray(face_pixels)
    image = image.resize(required_size)
    face_array = asarray(image)
    
    return face_array
    
# load images and extract faces for all images in a directory
def load_faces(directory, n_faces):
    # prepare model
    model = MTCNN()
    faces = list()
    
    for filename in os.listdir(directory):
        # Computing the retrieval and extraction of faces
        pixels = load_image(directory + filename)
        face = extract_face(model, pixels)
        if face is None:
            continue
        faces.append(face)
        print(len(faces), face.shape)
        if len(faces) >= n_faces:
            break
            
    return asarray(faces)

根据你的系统能力,接下来的步骤可能需要一些时间来完全计算。数据集中有大量的数据可用。如果读者有更多的时间和计算资源,最好是提取整个数据,在完整的CelebA数据集上进行训练。然而,为了本文的目的,我将只利用10000张图片进行个人训练。使用下面的代码片段,我们可以提取数据并将其保存为.npz压缩格式以备将来使用:

# load and extract all faces
directory = 'img_align_celeba/img_align_celeba/'
all_faces = load_faces(directory, 10000)
print('Loaded: ', all_faces.shape)

# save in compressed format
savez_compressed('img_align_celeba_128.npz', all_faces)

保存的数据可以按照下面的代码片断加载:

# load the prepared dataset
from numpy import load
data = load('img_align_celeba_128.npz')
faces = data['arr_0']
print('Loaded: ', faces.shape)

构建基本功能

在这一节中,我们将着重于构建我们之前在了解ProGANs如何工作时讨论的所有功能。我们将首先构建像素归一化函数,使我们能够将每个像素的特征向量归一到单位长度。下面的代码片断可用于计算相应的像素归一化:

# pixel-wise feature vector normalization layer
class PixelNormalization(Layer):
    # initialize the layer
    def __init__(self, **kwargs):
        super(PixelNormalization, self).__init__(**kwargs)
 
    # perform the operation
    def call(self, inputs):
        # computing pixel values
        values = inputs**2.0
        mean_values = backend.mean(values, axis=-1, keepdims=True)
        mean_values += 1.0e-8
        l2 = backend.sqrt(mean_values)
        normalized = inputs / l2
        return normalized
 
    # define the output shape of the layer
    def compute_output_shape(self, input_shape):
        return input_shape

在我们之前讨论的下一个重要方法中,我们将确保模型在迷你批次的标准偏差上进行训练。小批量功能只在判别器网络的输出层中利用。我们利用迷你批标准差来确保模型考虑较小的批次,使生成的图像种类更加独特。下面是计算迷你批标准差的代码片段:

# mini-batch standard deviation layer
class MinibatchStdev(Layer):
    # initialize the layer
    def __init__(self, **kwargs):
        super(MinibatchStdev, self).__init__(**kwargs)
 
    # perform the operation
    def call(self, inputs):
        mean = backend.mean(inputs, axis=0, keepdims=True)
        squ_diffs = backend.square(inputs - mean)
        mean_sq_diff = backend.mean(squ_diffs, axis=0, keepdims=True)
        mean_sq_diff += 1e-8
        stdev = backend.sqrt(mean_sq_diff)
        
        mean_pix = backend.mean(stdev, keepdims=True)
        shape = backend.shape(inputs)
        output = backend.tile(mean_pix, (shape[0], shape[1], shape[2], 1))
        
        combined = backend.concatenate([inputs, output], axis=-1)
        return combined
 
    # define the output shape of the layer
    def compute_output_shape(self, input_shape):
        input_shape = list(input_shape)
        input_shape[-1] += 1
        return tuple(input_shape)

在下一步,我们将计算加权和,并定义Wasserstein损失函数。如前所述,加权和类将被顺利地利用于各层的消退。我们将用alphaalpha值计算输出,正如我们在上一节中制定的那样。下面是以下动作的代码块:

# weighted sum output
class WeightedSum(Add):
    # init with default value
    def __init__(self, alpha=0.0, **kwargs):
        super(WeightedSum, self).__init__(**kwargs)
        self.alpha = backend.variable(alpha, name='ws_alpha')
 
    # output a weighted sum of inputs
    def _merge_function(self, inputs):
        # only supports a weighted sum of two inputs
        assert (len(inputs) == 2)
        # ((1-a) * input1) + (a * input2)
        output = ((1.0 - self.alpha) * inputs[0]) + (self.alpha * inputs[1])
        return output

# calculate wasserstein loss
def wasserstein_loss(y_true, y_pred):
    return backend.mean(y_true * y_pred)

最后,我们将定义一些基本功能,这些功能是为图像合成项目创建ProGAN网络结构所需要的。我们将定义函数,为生成器和鉴别器网络生成真实和虚假样本。然后,我们将更新淡入值并相应地扩展数据集。所有这些步骤都定义在各自的函数中,可从下面定义的代码段中获得:

# load dataset
def load_real_samples(filename):
    data = load(filename)
    X = data['arr_0']
    X = X.astype('float32')
    X = (X - 127.5) / 127.5
    return X
 
# select real samples
def generate_real_samples(dataset, n_samples):
    ix = randint(0, dataset.shape[0], n_samples)
    X = dataset[ix]
    y = ones((n_samples, 1))
    return X, y
 
# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
    x_input = randn(latent_dim * n_samples)
    x_input = x_input.reshape(n_samples, latent_dim)
    return x_input
 
# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
    x_input = generate_latent_points(latent_dim, n_samples)
    X = generator.predict(x_input)
    y = -ones((n_samples, 1))
    return X, y
 
# update the alpha value on each instance of WeightedSum
def update_fadein(models, step, n_steps):
    alpha = step / float(n_steps - 1)
    for model in models:
        for layer in model.layers:
            if isinstance(layer, WeightedSum):
                backend.set_value(layer.alpha, alpha)
                
# scale images to preferred size
def scale_dataset(images, new_shape):
    images_list = list()
    for image in images:
        new_image = resize(image, new_shape, 0)
        images_list.append(new_image)
    return asarray(images_list)

创建生成器网络

生成器架构包括一个潜在向量空间,我们可以在这里初始化我们的初始参数以生成所需的图像。一旦我们定义了潜向量空间,我们将得到一个4×4的维度,使我们能够处理初始输入图像。然后,我们将继续添加上采样和卷积层以及像素归一化层的几个区块,并使用泄漏的ReLU激活函数。最后,我们将添加一个1 x 1卷积来映射RGB图像。

除了一些小的例外,开发的生成器将利用研究论文中的大部分特征。我们将使用128个过滤器,而不是使用512个或不断增加的过滤器,因为我们是在用较小的图像尺寸构建架构。我们将利用高斯随机数和最大规范权重约束来代替均衡学习率。我们将首先定义一个发生器块,然后开发整个发生器模型网络。下面是生成器块的代码片段:

# adding a generator block
def add_generator_block(old_model):
    init = RandomNormal(stddev=0.02)
    const = max_norm(1.0)
    block_end = old_model.layers[-2].output
    
    # upsample, and define new block
    upsampling = UpSampling2D()(block_end)
    g = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(upsampling)
    g = PixelNormalization()(g)
    g = LeakyReLU(alpha=0.2)(g)
    g = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(g)
    g = PixelNormalization()(g)
    g = LeakyReLU(alpha=0.2)(g)
    
    out_image = Conv2D(3, (1,1), padding='same', kernel_initializer=init, kernel_constraint=const)(g)
    model1 = Model(old_model.input, out_image)
    out_old = old_model.layers[-1]
    out_image2 = out_old(upsampling)
    
    merged = WeightedSum()([out_image2, out_image])
    model2 = Model(old_model.input, merged)
    return [model1, model2]

下面是完成每个连续层的发生器架构的代码片段:

# define generator models
def define_generator(latent_dim, n_blocks, in_dim=4):
    init = RandomNormal(stddev=0.02)
    const = max_norm(1.0)
    model_list = list()
    in_latent = Input(shape=(latent_dim,))
    g  = Dense(128 * in_dim * in_dim, kernel_initializer=init, kernel_constraint=const)(in_latent)
    g = Reshape((in_dim, in_dim, 128))(g)
    
    # conv 4x4, input block
    g = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(g)
    g = PixelNormalization()(g)
    g = LeakyReLU(alpha=0.2)(g)
    
    # conv 3x3
    g = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(g)
    g = PixelNormalization()(g)
    g = LeakyReLU(alpha=0.2)(g)
    
    # conv 1x1, output block
    out_image = Conv2D(3, (1,1), padding='same', kernel_initializer=init, kernel_constraint=const)(g)
    model = Model(in_latent, out_image)
    model_list.append([model, model])
    
    for i in range(1, n_blocks):
        old_model = model_list[i - 1][0]
        models = add_generator_block(old_model)
        model_list.append(models)
        
    return model_list

创建判别器网络

对于鉴别器架构,我们将对生成器网络的构建方式进行某种程度的反向工程。我们将从一个RGB图像开始,通过一堆卷积层进行降采样。一堆区块将重复这一模式,但在最后,在输出区块,我们将添加一个与之前的输出相连接的迷你标准偏差层。最后,在两个额外的卷积层之后,我们将使判别器的输出成为一个单一的输出,它决定了生成的图像是假的还是真的。下面是添加判别器块的代码片段。

# adding a discriminator block
def add_discriminator_block(old_model, n_input_layers=3):
    init = RandomNormal(stddev=0.02)
    const = max_norm(1.0)
    in_shape = list(old_model.input.shape)
    
    # define new input shape as double the size
    input_shape = (in_shape[-2]*2, in_shape[-2]*2, in_shape[-1])
    in_image = Input(shape=input_shape)
    
    # define new input processing layer
    d = Conv2D(128, (1,1), padding='same', kernel_initializer=init, kernel_constraint=const)(in_image)
    d = LeakyReLU(alpha=0.2)(d)
    
    # define new block
    d = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = AveragePooling2D()(d)
    block_new = d
    
    # skip the input, 1x1 and activation for the old model
    for i in range(n_input_layers, len(old_model.layers)):
        d = old_model.layers[i](d)
    model1 = Model(in_image, d)
    
    model1.compile(loss=wasserstein_loss, optimizer=Adam(lr=0.001, beta_1=0, beta_2=0.99, epsilon=10e-8))
    
    downsample = AveragePooling2D()(in_image)
    
    block_old = old_model.layers[1](downsample)
    block_old = old_model.layers[2](block_old)
    d = WeightedSum()([block_old, block_new])
    
    for i in range(n_input_layers, len(old_model.layers)):
        d = old_model.layers[i](d)
        
    model2 = Model(in_image, d)
    
    model2.compile(loss=wasserstein_loss, optimizer=Adam(lr=0.001, beta_1=0, beta_2=0.99, epsilon=10e-8))
    return [model1, model2]

下面是完成每个连续层的判别器架构的代码片段:

# define the discriminator models for each image resolution
def define_discriminator(n_blocks, input_shape=(4,4,3)):
    init = RandomNormal(stddev=0.02)
    const = max_norm(1.0)
    model_list = list()
    in_image = Input(shape=input_shape)
    
    d = Conv2D(128, (1,1), padding='same', kernel_initializer=init, kernel_constraint=const)(in_image)
    d = LeakyReLU(alpha=0.2)(d)
    d = MinibatchStdev()(d)
    
    d = Conv2D(128, (3,3), padding='same', kernel_initializer=init, kernel_constraint=const)(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(128, (4,4), padding='same', kernel_initializer=init, kernel_constraint=const)(d)
    d = LeakyReLU(alpha=0.2)(d)
    
    d = Flatten()(d)
    out_class = Dense(1)(d)
    
    model = Model(in_image, out_class)
    model.compile(loss=wasserstein_loss, optimizer=Adam(lr=0.001, beta_1=0, beta_2=0.99, epsilon=10e-8))
    model_list.append([model, model])
    
    for i in range(1, n_blocks):
        old_model = model_list[i - 1][0]
        models = add_discriminator_block(old_model)
        model_list.append(models)
        
    return model_list

开发ProGAN模型架构

一旦我们完成了对单个生成器和鉴别器网络的定义,我们将创建一个整体的复合模型,将两者结合起来,创建ProGAN模型架构。一旦我们结合了生成器模型,我们就可以对它们进行编译并进行相应的训练。让我们定义函数来创建复合的ProGAN网络:

# define composite models for training generators via discriminators

def define_composite(discriminators, generators):
    model_list = list()
    # create composite models
    for i in range(len(discriminators)):
        g_models, d_models = generators[i], discriminators[i]
        # straight-through model
        d_models[0].trainable = False
        model1 = Sequential()
        model1.add(g_models[0])
        model1.add(d_models[0])
        model1.compile(loss=wasserstein_loss, optimizer=Adam(lr=0.001, beta_1=0, beta_2=0.99, epsilon=10e-8))
        # fade-in model
        d_models[1].trainable = False
        model2 = Sequential()
        model2.add(g_models[1])
        model2.add(d_models[1])
        model2.compile(loss=wasserstein_loss, optimizer=Adam(lr=0.001, beta_1=0, beta_2=0.99, epsilon=10e-8))
        # store
        model_list.append([model1, model2])
    return model_list

最后,一旦我们创建了整体的复合模型,我们就可以开始训练过程。训练网络的大部分步骤与我们以前训练GAN的方式相似。然而,在训练过程中也引入了淡入层和逐步增长的GAN的渐进式更新。下面是创建训练纪元的代码块:

# train a generator and discriminator
def train_epochs(g_model, d_model, gan_model, dataset, n_epochs, n_batch, fadein=False):
    bat_per_epo = int(dataset.shape[0] / n_batch)
    n_steps = bat_per_epo * n_epochs
    half_batch = int(n_batch / 2)
    
    for i in range(n_steps):
        # update alpha for all WeightedSum layers when fading in new blocks
        if fadein:
            update_fadein([g_model, d_model, gan_model], i, n_steps)
        # prepare real and fake samples
        X_real, y_real = generate_real_samples(dataset, half_batch)
        X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
        
        # update discriminator model
        d_loss1 = d_model.train_on_batch(X_real, y_real)
        d_loss2 = d_model.train_on_batch(X_fake, y_fake)
        
        # update the generator via the discriminator's error
        z_input = generate_latent_points(latent_dim, n_batch)
        y_real2 = ones((n_batch, 1))
        g_loss = gan_model.train_on_batch(z_input, y_real2)
        
        # summarize loss on this batch
        print('>%d, d1=%.3f, d2=%.3f g=%.3f' % (i+1, d_loss1, d_loss2, g_loss))
        
# train the generator and discriminator
def train(g_models, d_models, gan_models, dataset, latent_dim, e_norm, e_fadein, n_batch):
    g_normal, d_normal, gan_normal = g_models[0][0], d_models[0][0], gan_models[0][0]
    gen_shape = g_normal.output_shape
    scaled_data = scale_dataset(dataset, gen_shape[1:])
    print('Scaled Data', scaled_data.shape)

    # train normal or straight-through models
    train_epochs(g_normal, d_normal, gan_normal, scaled_data, e_norm[0], n_batch[0])
    summarize_performance('tuned', g_normal, latent_dim)
    
    # process each level of growth
    for i in range(1, len(g_models)):
        # retrieve models for this level of growth
        [g_normal, g_fadein] = g_models[i]
        [d_normal, d_fadein] = d_models[i]
        [gan_normal, gan_fadein] = gan_models[i]
        
        # scale dataset to appropriate size
        gen_shape = g_normal.output_shape
        scaled_data = scale_dataset(dataset, gen_shape[1:])
        print('Scaled Data', scaled_data.shape)
        
        # train fade-in models for next level of growth
        train_epochs(g_fadein, d_fadein, gan_fadein, scaled_data, e_fadein[i], n_batch[i], True)
        summarize_performance('faded', g_fadein, latent_dim)
        
        # train normal or straight-through models
        train_epochs(g_normal, d_normal, gan_normal, scaled_data, e_norm[i], n_batch[i])
        summarize_performance('tuned', g_normal, latent_dim)

在训练过程中,我们还将定义一个自定义函数,这将有助于我们对结果进行相应的评估。我们可以总结我们的性能,以及绘制每个迭代产生的一些数字,以了解我们对结果的改进程度。下面是总结我们整体模型性能的代码片段:

# generate samples and save as a plot and save the model
def summarize_performance(status, g_model, latent_dim, n_samples=25):
    gen_shape = g_model.output_shape
    name = '%03dx%03d-%s' % (gen_shape[1], gen_shape[2], status)
    
    X, _ = generate_fake_samples(g_model, latent_dim, n_samples)
    X = (X - X.min()) / (X.max() - X.min())
    
    square = int(sqrt(n_samples))
    for i in range(n_samples):
        pyplot.subplot(square, square, 1 + i)
        pyplot.axis('off')
        pyplot.imshow(X[i])
        
    # save plot to file
    filename1 = 'plot_%s.png' % (name)
    pyplot.savefig(filename1)
    pyplot.close()
    
    filename2 = 'model_%s.h5' % (name)
    g_model.save(filename2)
    print('>Saved: %s and %s' % (filename1, filename2))

最后,一旦所有必要的函数得到相应的定义,我们就可以开始训练过程。为了提高稳定性,我们将对小尺寸的图像使用较大的批处理量和较少的历时,而对较大比例的图像则减少批处理量和增加历时数。训练用于图像合成的ProGAN网络的代码片段如下所示:

# number of growth phases where 6 blocks == [4, 8, 16, 32, 64, 128]
n_blocks = 6
latent_dim = 100

d_models = define_discriminator(n_blocks)
g_models = define_generator(latent_dim, n_blocks)
gan_models = define_composite(d_models, g_models)

dataset = load_real_samples('img_align_celeba_128.npz')
print('Loaded', dataset.shape)

n_batch = [16, 16, 16, 8, 4, 4]
n_epochs = [5, 8, 8, 10, 10, 10]

train(g_models, d_models, gan_models, dataset, latent_dim, n_epochs, n_epochs, n_batch)

结果和进一步讨论

>12500, d1=1756536064.000, d2=8450036736.000 g=-378913792.000
Saved: plot_032x032-faded.png and model_032x032-faded.h5

大部分的代码都是从机器学习资源库网站上考虑的,我强烈建议从以下链接中查看。在以下项目中,可以进行一些改进和补充。我们可以通过使用更高的图像质量、增加训练迭代和整体计算能力来改进数据集。另一个想法是将ProGAN网络与SRGAN架构合并,创造独特的组合可能性。我建议感兴趣的读者对众多可能的结果进行实验。


结语

生物的一切学习都是以小步(或婴儿)为单位,要么从以前的错误中适应,要么从头开始学习,同时慢慢发展任何特定概念、想法或想象力的每个单独方面。我们不是直接创造大的句子,而是先学习字母和英文字母、小词,以此类推,直到我们能够构建更长的句子。同样,ProGAN网络也采用了类似的直观方法,模型从最低像素分辨率开始学习。然后,它逐渐以更高的分辨率的模式进行学习,在最后达到高质量的结果。

在这篇文章中,我们涵盖了对高质量图像生成的ProGAN网络获得基本直观理解所需的大部分主题。我们从ProGAN网络的基本介绍开始,然后继续了解其作者在其研究论文中介绍的大部分独特方面,以完成前所未有的结果。最后,我们利用所获得的知识,从头开始构建了一个ProGAN网络,用于使用Celeb-A数据集生成面部图像。虽然训练是在有限的分辨率下进行的,但读者可以进行进一步的实验,一直到更高的分辨率质量。这些生成网络有无穷无尽的进步的可能性。

在接下来的文章中,我们将介绍生成式对抗网络的更多变化,比如StyleGAN架构等等。我们还将获得对变异自动编码器的概念性理解,以及在更多的信号处理项目中工作。在那之前,请继续尝试和编码更多独特的深度学习和人工智能项目吧

今天就为你的机器学习工作流程增加速度和简单性吧