作者 | Nagesh Singh Chauhan
来源 | Medium
编辑 | 代码医生团队
“过去20年来,深度学习中最酷的想法。” — GAN上的Yann LeCun。
为什么首先开发GAN?
已经注意到,大多数主流神经网络都可以通过仅向原始数据中添加少量噪声而轻易地将其分类错误。出乎意料的是,添加噪声后的模型比正确预测的模型对错误预测的置信度更高。造成这种攻击的原因是,大多数机器学习模型都是从有限的数据中学习的,这是一个巨大的缺点,因为它很容易过度拟合。同样,输入和输出之间的映射几乎是线性的。尽管看起来各个类之间的分隔边界似乎是线性的,但实际上它们是由线性组成的,即使特征空间中某个点的微小变化也可能导致数据分类错误。
GAN如何工作?
GAN通过使两个神经网络相互竞争来学习数据集的概率分布。
一个叫做Generator的神经网络生成新的数据实例,而另一个Discriminator评估它们的真实性。即,鉴别器决定它检查的数据的每个实例是否属于实际的训练数据集。
同时,生成器正在创建新的合成/伪造图像,并将其传递给鉴别器。这样做是为了希望即使它们是假的,它们也将被认为是真实的。伪图像是使用称为转置卷积的卷积逆运算从100维噪声(在-1.0到1.0之间的均匀分布)生成的。
生成器的目标是生成可传递的图像:说谎而不被捕获。鉴别器的目的是将来自发生器的图像识别为伪造的。
以下是GAN采取的步骤:
因此,有一个双重反馈循环:
GAN背后的数学
更深入地了解它如何在数学上起作用。判别器的工作是执行二进制分类以在真实与伪造之间进行检测,因此其损失函数为二进制交叉熵。生成器所做的就是从噪声到真实数据的密度估计,并将其馈送到鉴别器以使其模糊。
设计中采用的方法是将其建模为MiniMax游戏。现在看一下成本函数:
中的第一项J(D)表示将实际数据提供给鉴别器,鉴别器将要最大化预测一个的对数概率,从而表明数据是真实的。第二项代表由生成的样本G。在此,鉴别器将要最大化预测零的对数概率,这表明数据是伪造的。另一方面,生成器试图使鉴别器正确的对数概率最小。这个问题的解决方案是博弈的平衡点,这是鉴别器损失的鞍点。
目标功能
GAN的架构
D()给出给定样本来自训练数据X的概率。对于生成器,要最小化,log(1-D(G(z))即,当值D(G(z))很高时,D将假设G(z)X之外什么都不是,这使得它1-D(G(z))非常低,并且希望将其最小化,即使降低。对于鉴别器,要最大化D(X)和(1-D(G(z)))。因此,D的最佳状态为P(x)=0.5。但是,我们要训练生成器G,使其生成鉴别器D的结果,以使D无法区分z和X。
现在的问题是为什么这是一个极小极大函数?这是因为鉴别器试图最大化目标,而生成器试图最小化目标,由于这种最小化/最大化,得到了最小最大项。都通过交替的梯度下降一起学习。
尽管GAN的概念在理论上很简单,但要建立一个有效的模型却非常困难。在GAN中,有两个深层网络耦合在一起,使得反向传播梯度的难度提高了两倍。
深度卷积GAN(DCGAN)是演示如何构建实用的GAN的模型之一,该GAN可以自己学习如何合成新图像。DCGAN与GAN非常相似,但专门致力于使用深度卷积网络代替Vanilla GAN中使用的全连接网络。
卷积网络有助于发现图像中的深层相关性,即它们寻找空间相关性。这意味着DCGAN将是图像/视频数据的更好选择,而GAN可以被认为是DCGAN和许多其他体系结构(CGAN,CycleGAN,StarGAN等)已经发展起来的一般思想。
数据集
该数据集非常适合训练和测试用于面部检测的模型,尤其适用于识别面部特征,例如找到棕色头发的人,微笑着或戴眼镜的人。图像涵盖大的姿势变化,背景混乱,各种各样的人,并由大量的图像和丰富的注释提供支持。
数据集可以从Kaggle下载。目标是创建一个能够生成现实中不存在的逼真的人类图像的模型。
https://www.kaggle.com/jessicali9530/celeba-dataset
加载数据集并查看输入图像的外观:
from tqdm import tqdm
import numpy as np
import pandas as pd
import os
from matplotlib import pyplot as plt
PIC_DIR = './drive/img_align_celeba/'
IMAGES_COUNT = 10000
ORIG_WIDTH = 178
ORIG_HEIGHT = 208
diff = (ORIG_HEIGHT - ORIG_WIDTH) // 2
WIDTH = 128
HEIGHT = 128
crop_rect = (0, diff, ORIG_WIDTH, ORIG_HEIGHT - diff)
images = []
for pic_file in tqdm(os.listdir(PIC_DIR)[:IMAGES_COUNT]):
pic = Image.open(PIC_DIR + pic_file).crop(crop_rect)
pic.thumbnail((WIDTH, HEIGHT), Image.ANTIALIAS)
images.append(np.uint8(pic))
#Normalize the images
images = np.array(images) / 255
images.shape
#print first 25 images
plt.figure(1, figsize=(10, 10))
for i in range(25):
plt.subplot(5, 5, i+1)
plt.imshow(images[i])
plt.axis('off')
plt.show()
输入图像
下一步是创建一个Generator:
生成器则采用另一种方式:试图欺骗鉴别器的是艺术家。该网络由8个卷积层组成。在这里,首先接受输入,gen_input并将其输入到第一个卷积层中。每个卷积层都执行卷积,然后执行批归一化以及泄漏的ReLu。然后返回tanh激活函数。
LATENT_DIM = 32
CHANNELS = 3
def create_generator():
gen_input = Input(shape=(LATENT_DIM, ))
x = Dense(128 * 16 * 16)(gen_input)
x = LeakyReLU()(x)
x = Reshape((16, 16, 128))(x)
x = Conv2D(256, 5, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2D(512, 5, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2D(512, 5, padding='same')(x)
x = LeakyReLU()(x)
x = Conv2D(CHANNELS, 7, activation='tanh', padding='same')(x)
generator = Model(gen_input, x)
return generator
接下来,创建一个鉴别器:
鉴别器网络由与生成器相同的卷积层组成。对于网络的每个层,将进行卷积,然后将执行批归一化,以使网络更快,更准确,最后将执行Leaky ReLu。
def create_discriminator():
disc_input = Input(shape=(HEIGHT, WIDTH, CHANNELS))
x = Conv2D(256, 3)(disc_input)
x = LeakyReLU()(x)
x = Conv2D(256, 4, strides=2)(x)
x = LeakyReLU()(x)
x = Conv2D(256, 4, strides=2)(x)
x = LeakyReLU()(x)
x = Conv2D(256, 4, strides=2)(x)
x = LeakyReLU()(x)
x = Conv2D(256, 4, strides=2)(x)
x = LeakyReLU()(x)
x = Flatten()(x)
x = Dropout(0.4)(x)
x = Dense(1, activation='sigmoid')(x)
discriminator = Model(disc_input, x)
optimizer = RMSprop(
lr=.0001,
clipvalue=1.0,
decay=1e-8
)
discriminator.compile(
optimizer=optimizer,
loss='binary_crossentropy'
)
return discriminator
定义GAN模型:
接下来, 可以定义GAN模型,将生成器模型和鉴别器模型组合为一个更大的模型。此更大的模型将用于使用鉴别器模型计算的输出和误差来训练生成器中的模型权重。区分模型是单独训练的,因此,在此较大的GAN模型中,模型权重被标记为不可训练,以确保仅更新生成器模型的权重。鉴别器权重可训练性的此更改仅在训练组合GAN模型时影响,而在独立训练鉴别器时不起作用。
这个更大的GAN模型将潜在空间中的一个点作为输入,使用生成器模型生成图像,将其作为输入馈送到鉴别器模型,然后输出或分类为真实或伪造。
由于鉴别器的输出是S形的,因此我们使用二进制交叉熵来表示损失。在这种情况下,与Adam相比,RMSProp作为优化程序可生成更逼真的伪图像。学习率是0.0001。体重下降和限幅值可在训练的后期稳定学习。如果要调整学习率,则必须调整衰减。
GAN尝试复制概率分布。因此应该使用损失函数来反映GAN生成的数据的分布与实际数据的分布之间的距离。
generator = create_generator()
discriminator = create_discriminator()
discriminator.trainable = False
gan_input = Input(shape=(LATENT_DIM, ))
gan_output = discriminator(generator(gan_input))
gan = Model(gan_input, gan_output)
#Adversarial Model
optimizer = RMSprop(lr=.0001, clipvalue=1.0, decay=1e-8)
gan.compile(optimizer=optimizer, loss='binary_crossentropy')
不仅要定义一个损失函数,还需要定义三个:生成器的损失,使用实像时的鉴别器损失和使用假像时的鉴别器损失。伪造图像损失与真实图像损失之和就是总的鉴别器损失。
训练GAN模型:
训练是最困难的部分,并且由于GAN包含两个单独训练的网络,因此它的训练算法必须解决两个复杂问题:
随着生成器在训练中的改进,鉴别器的性能会变差,因为鉴别器无法轻易分辨真假之间的区别。如果生成器成功完成,则鉴别器的准确性为50%。实际上,判别器会掷硬币进行预测。
这种进展为GAN的整体收敛提出了一个问题:随着时间的流逝,鉴别器反馈的意义逐渐减弱。如果GAN继续进行训练,直到判别器给出完全随机的反馈,则生成器将开始根据垃圾反馈进行训练,其质量可能会下降。
iters = 20000
batch_size = 16
RES_DIR = 'res2'
FILE_PATH = '%s/generated_%d.png'
if not os.path.isdir(RES_DIR):
os.mkdir(RES_DIR)
CONTROL_SIZE_SQRT = 6
control_vectors = np.random.normal(size=(CONTROL_SIZE_SQRT**2, LATENT_DIM)) / 2
start = 0
d_losses = []
a_losses = []
images_saved = 0
for step in range(iters):
start_time = time.time()
latent_vectors = np.random.normal(size=(batch_size, LATENT_DIM))
generated = generator.predict(latent_vectors)
real = images[start:start + batch_size]
combined_images = np.concatenate([generated, real])
labels = np.concatenate([np.ones((batch_size, 1)), np.zeros((batch_size, 1))])
labels += .05 * np.random.random(labels.shape)
d_loss = discriminator.train_on_batch(combined_images, labels)
d_losses.append(d_loss)
latent_vectors = np.random.normal(size=(batch_size, LATENT_DIM))
misleading_targets = np.zeros((batch_size, 1))
a_loss = gan.train_on_batch(latent_vectors, misleading_targets)
a_losses.append(a_loss)
start += batch_size
if start > images.shape[0] - batch_size:
start = 0
if step % 50 == 49:
gan.save_weights('gan.h5')
print('%d/%d: d_loss: %.4f, a_loss: %.4f. (%.1f sec)' % (step + 1, iters, d_loss, a_loss, time.time() - start_time))
control_image = np.zeros((WIDTH * CONTROL_SIZE_SQRT, HEIGHT * CONTROL_SIZE_SQRT, CHANNELS))
control_generated = generator.predict(control_vectors)
for i in range(CONTROL_SIZE_SQRT ** 2):
x_off = i % CONTROL_SIZE_SQRT
y_off = i // CONTROL_SIZE_SQRT
control_image[x_off * WIDTH:(x_off + 1) * WIDTH, y_off * HEIGHT:(y_off + 1) * HEIGHT, :] = control_generated[i, :, :, :]
im = Image.fromarray(np.uint8(control_image * 255))
im.save(FILE_PATH % (RES_DIR, images_saved))
images_saved += 1
还对已生成的输出图像进行GIF处理。
import imageio
import shutil
images_to_gif = []
for filename in os.listdir(RES_DIR):
images_to_gif.append(imageio.imread(RES_DIR + '/' + filename))
imageio.mimsave('trainnig_visual.gif', images_to_gif)
shutil.rmtree(RES_DIR)
https://giphy.com/gifs/QuDO8a4UBfyZrLP331?utm_source=iframe&utm_medium=embed&utm_campaign=Embeds&utm_term=https%3A%2F%2Fcdn.embedly.com%2Fwidgets%2Fmedia.html%3Fsrc%3Dhttps%3A%2F%2Fgiphy.com%2Fembed%2FQuDO8a4UBfyZrLP331%2Ftwitter%2Fiframe&%3Bdisplay_name=Giphy&%3Burl=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FQuDO8a4UBfyZrLP331%2Fgiphy.gif&%3Bimage=https%3A%2F%2Fi.giphy.com%2Fmedia%2FQuDO8a4UBfyZrLP331%2Fgiphy-downsized-large.gif&%3Bkey=a19fcc184b9711e1b4764040d3dc5c07&%3Btype=text%2Fhtml&%3Bschema=giphy
可以在GitHub存储库中获取代码。
https://github.com/nageshsinghc4/Face-generation-GAN
只是看到了一个模型,如果经过足够的训练,它将如何生成几乎像人的面孔。由于计算限制,已经为模型训练了15000个纪元。可以尝试更多的时期以获得更好的结果。
随着时间的流逝,周围存在的这些算法在做事上会越来越好,这意味着这些生成模型在生成模仿对象方面可能会变得更好。很有可能另一个崭新的生成模型即将出现。
结论:GAN的未来
无监督学习是人工智能的下一个前沿领域,正在朝着它前进。
GAN和生成模型一般都很有趣且令人困惑。它们越来越依赖人工智能的世界迈出了又一步。GAN在诸如生成图像数据集示例,生成真实照片,图像到图像翻译,文本到图像翻译,语义图像到照片翻译,面部正面视图生成,生成等情况下具有大量应用程序新的人类姿势,面部老化,视频预测,3D对象生成等。