AI可以生成以假乱真的假图像甚至假视频的新闻早已不是新鲜事,这一切都得益于GAN网络。除了生成这些逼真的图像,它还能修复破损图像或者扩展当前图像。不难想象,未来它可能不仅能生成高分辨率的精确图像,还能够创建整段视频。这对摄影技术来说,到底是好是坏呢?
每当我们听到「人工智能」、「机器学习」或者「机器人」这样的词汇时,大多数人都会很容易联想到一个像科幻电影中那种能够行走、说话的机器人,然后不由自主地想象遥远的未来。
图源:Giphy
拜托,醒醒吧!实际上,人工智能技术已经伴随我们很多年了,现在它们正逐渐被嵌入到你的智能手机(比如Siri、谷歌助手。)以及汽车的 GPS 系统中,甚至在你阅读完这篇文章后,它还会想到接下来要向你推荐哪篇文章。然而,在过去几年中,没有哪个领域像计算机视觉这样受到了如此大的影响。
随着技术的进步,非常吸引视觉的超高分辨率图像变得越来越普遍。人们不必再学习使用 Photoshop 或 CorelDRAW 这样的工具来增强和修改图像了。为了得到尽可能好的图像,人工智能技术已经被应用到了图像增强和操作的各个方面。然而,最新涌现出来的想法实际上是使用人工智能来进行合成,从而生成图像。
以前,几乎每一张你看过的图像都是被拍摄下来或者由人手动创作的。可能有数百种手动生成图像的工具,但是它们都需要由人来主导这个过程。然而,想象一下,如果一个计算机程序可以从零开始绘制出你想要它画的所有内容会怎样?微软的 Drawing Bot 可能是第一个也是唯一一个实现了这个目标的技术。想象一下,在不久的将来,你可以直接在智能手机上下载一个应用程序,然后给它发出类似「我想要一张我站在埃菲尔铁塔旁边的照片」的命令(不过要确保你的表述是正确的)。
图源:Blazepress
生成对抗网络
「GAN 是过去十年中机器学习领域最有趣的想法!」——Yann LeCun
生成这种合成图像的基础在于生成对抗网络。自 2014 年 Ian Goodfellow 和他的同事发现并在论文《Generative Adversarial Networks》中发表GAN以来,GAN 就一直是深度学习中最具吸引力且应用最广泛的方法之一。这项技术无止尽的应用是对抗训练的核心,它不仅包括计算机视觉,还包括数据分析、机器人技术和预测建模领域。
那么 GAN 的神奇之处究竟在哪里呢?
生成对抗网络属于生成模型的范畴。这意味着 GAN 的工作是在完全自动化的过程中创建或「生成」新数据。
Goodfellow 的论文中给出的生成图像示例
顾名思义,GAN 实际上是由两个独立的相互竞争(以对抗的方式)的神经网络组成的。其中一个神经网络是生成器,它从随机噪声中生成新的数据实例,而另一个神经网络称为判别器,它对这些实例的真伪进行评估。换句话说,判别器会判定它检查的每个数据实例是否属于真实的训练数据集。
一个简单的例子
假设你的任务是仿造一个著名艺术家画的画。但你不知道这位艺术家是谁,甚至也没见过他的画。但是你需要伪造一幅他的画,并作为原作之一在拍卖会上展出。所以,你决定试一试。你需要的所有材料就是一些颜料和画布,对吧?然而,拍卖商不希望有赝品,他们只想要真品,所以他们雇佣了一名侦探,他将首先核实拍卖会上出现的所有展品。而且,侦探有该艺术家原作的样本,所以如果你拿出的是自己随便仿造的画,他马上就会知道这不是原作。
图源:GitHub
当他否定了这幅画后,你决定再试一次。但这一次,你获得了一些有用提示,因为侦探在评估你的画时透露了一些关于这幅画的信息。
当你再尝试的时候,你画出的画应该会好一点。但侦探还是没有被说服,又拒绝了你。所以你一次又一次地尝试,每次利用某种形式的反馈来修正这幅画,让它变得越来越好(假设侦探不介意你没完没了地把画拿回来)。最后,经过一千多次的尝试,你终于能够做出近乎完美的复制品。当侦探看着他的样画时,他不确定你递给他的是真迹,还是与这位著名艺术家风格和笔触相同的其它东西。
GAN 的工作流程是怎样的?
将相同的思路应用到一个神经网络组合上,我们可以得到如下的 GAN 训练过程:
基础的 GAN 框架(图源:Medium)
生成器最初接收一些随机噪声并将其传递给判别器。
因为判别器可以访问真实图像的数据集,所以它将它们与从生成器那里接收到的图像进行比较,并评估其真实性。
由于初始图像只是随机噪声,所以此时生成器的输出将被评估为假的。
生成器通过改变参数来不断尝试,以便生成更真实的图像。
随着训练的进行,这两个网络都会变得越来越聪明。
最后,生成器会创建一个与真实图像数据集中的图像难以区分的图像。而这个判别器不够聪明,无法分辨出给定的图像是真还是假。
此时,训练结束,生成的图像就是我们的最终结果。
GAN生成汽车标识图像的过程
是时候看看代码了
下面是一个在PyTorch中实现的基本生成网络:
import argparse import os import numpy as np import math import torchvision.transforms as transforms from torchvision.utils import save_image from torch.utils.data import DataLoader from torchvision import datasets from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch os.makedirs('images', exist_ok=True) parser = argparse.ArgumentParser() parser.add_argument('--n_epochs', type=int, default=200, help='number of epochs of training') parser.add_argument('--batch_size', type=int, default=64, help='size of the batches') parser.add_argument('--lr', type=float, default=0.0002, help='adam: learning rate') parser.add_argument('--b1', type=float, default=0.5, help='adam: decay of first order momentum of gradient') parser.add_argument('--b2', type=float, default=0.999, help='adam: decay of first order momentum of gradient') parser.add_argument('--n_cpu', type=int, default=8, help='number of cpu threads to use during batch generation') parser.add_argument('--latent_dim', type=int, default=100, help='dimensionality of the latent space') parser.add_argument('--img_size', type=int, default=28, help='size of each image dimension') parser.add_argument('--channels', type=int, default=1, help='number of image channels') parser.add_argument('--sample_interval', type=int, default=400, help='interval betwen image samples') opt = parser.parse_args() print(opt) img_shape = (opt.channels, opt.img_size, opt.img_size) cuda = True if torch.cuda.is_available() else False class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() def block(in_feat, out_feat, normalize=True): layers = [nn.Linear(in_feat, out_feat)] if normalize: layers.append(nn.BatchNorm1d(out_feat, 0.8)) layers.append(nn.LeakyReLU(0.2, inplace=True)) return layers self.model = nn.Sequential( *block(opt.latent_dim, 128, normalize=False), *block(128, 256), *block(256, 512), *block(512, 1024), nn.Linear(1024, int(np.prod(img_shape))), nn.Tanh() ) def forward(self, z): img = self.model(z) img = img.view(img.size(0), *img_shape) return img class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() self.model = nn.Sequential( nn.Linear(int(np.prod(img_shape)), 512), nn.LeakyReLU(0.2, inplace=True), nn.Linear(512, 256), nn.LeakyReLU(0.2, inplace=True), nn.Linear(256, 1), nn.Sigmoid() ) def forward(self, img): img_flat = img.view(img.size(0), -1) validity = self.model(img_flat) return validity # Loss function adversarial_loss = torch.nn.BCELoss() # Initialize generator and discriminator generator = Generator() discriminator = Discriminator() if cuda: generator.cuda() discriminator.cuda() adversarial_loss.cuda() # Configure data loader os.makedirs('../../data/mnist', exist_ok=True) dataloader = torch.utils.data.DataLoader( datasets.MNIST('../../data/mnist', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ])), batch_size=opt.batch_size, shuffle=True) # Optimizers optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2)) optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2)) Tensor = torch.cuda.FloatTensor if cuda else torch.FloatTensor # ---------- # Training # ---------- for epoch in range(opt.n_epochs): for i, (imgs, _) in enumerate(dataloader): # Adversarial ground truths valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False) fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False) # Configure input real_imgs = Variable(imgs.type(Tensor)) # ----------------- # Train Generator # ----------------- optimizer_G.zero_grad() # Sample noise as generator input z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim)))) # Generate a batch of images gen_imgs = generator(z) # Loss measures generator's ability to fool the discriminator g_loss = adversarial_loss(discriminator(gen_imgs), valid) g_loss.backward() optimizer_G.step() # --------------------- # Train Discriminator # --------------------- optimizer_D.zero_grad() # Measure discriminator's ability to classify real from generated samples real_loss = adversarial_loss(discriminator(real_imgs), valid) fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake) d_loss = (real_loss + fake_loss) / 2 d_loss.backward() optimizer_D.step() print ("[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]" % (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())) batches_done = epoch * len(dataloader) + i if batches_done % opt.sample_interval == 0: save_image(gen_imgs.data[:25], 'images/%d.png' % batches_done, nrow=5, normalize=True)
优点和缺点
和其它所有的技术一样,GAN 也有其特有的优缺点。本文将在不深入研究细节的情况下总结 GAN 的一些优缺点:
以下是 GAN 的一些潜在优势:
GAN 并不一定要用带标签的样本来训练。
由于 GAN 不需要在样本中依次生成不同的条目,它们比与其它生成模型(如信念网络)生成样本的速度更快。
GAN比使用蒙特卡洛方法逼近对数配分函数( log partition function)梯度的生成模型更易训练。因为蒙特卡洛方法在高维空间中不能很好地工作,这样的生成模型不能执行像使用 ImageNet 进行训练这样的现实任务。
GAN 不需要引入任何决定性偏置(deterministic bias)。某些像变分自编码器这样的生成方法引入了决定性偏置,因为它们优化了对数似然的下界,而不是似然本身。这似乎导致变分自编码器生成的实例比 GAN 生成的实例更模糊。
同时,GAN 也具有下列缺点:GAN 很难训练。这些网络试图优化的函数是本质上没有封闭形式的损失函数(不像对数损失或平方误差这样的标准损失函数)。因此,优化这种损失函数是非常困难的,需要对网络架构和训练协议进行大量的反复试错。
特别是对于图像生成任务,目前还没有合适的测量方法来评估准确率。由于合成的图像对于计算机本身来说可能是可以接受的,因此评估实际的生成结果是一个非常主观的问题,取决于人类观测者的看法。因此我们现在可以用「Inception Score」和「 Frechet Inception Distance」这样的函数来衡量它们的性能。
GAN 的应用
有趣的部分来了!下面将列举出我们可以使用 GAN 做的所有神奇之事。在所有潜在的用法中,GAN 在计算机视觉领域有着广泛的应用。
文本到图像的转换
对于这一概念,有很多实现方法,如 TAC-GAN(以文本为条件的辅助分类器生成对抗网络) 。它们被用于根据文本描述合成对应的图像。
图左:TAC-GAN 架构。图右:将一行文本输入网络后生成的结果。
领域迁移
GAN 在风格迁移等工作中非常受欢迎。它包括使用特殊类型GAN(称为 CGAN,条件生成对抗网络)的图像到图像迁移。绘画和概念设计从未变得像现在这么简单。但是,虽然 GAN 可以根据下面这个手提包的草图完成像这样的简单绘画,但是画更复杂的东西(比如完美的人脸)目前还不是GAN 的强项。事实上,它对某些事物的生成结果相当可怕。
CGAN pix2pix 的生成结果(图源:GitHub)
图像修复(Inpainting )和扩展(Outpainting)
生成网络两个令人激动的应用是图像修复和图像扩展。图像补全包括填补图像中的缺失部分或噪声,这可以被看做是对图像的修补。例如,给定一张有孔洞或缺口的图像,GAN 应该能够以一种「可接受」的方式来修正它。另一方面,图像扩展涉及使用网络自身的学习能力来想象图像在当前的边界之外应该是什么样子。
图像修复(图左)和图像扩展(图右)的生成结果(图源Githu)
人脸图像合成
得益于生成网络,人脸合成成为了可能,它包括从不同角度生成单张人脸图像。这也解释了为什么面部识别系统不需要数百张人脸样本,而是仅使用一张就能识别出人脸来。不仅如此,生成假的人脸图像也成为了可能。英伟达最近基于Celeba Hq 数据集,使用GAN 2.0 生成了高分辨率的假人脸,这是第一个以高分辨率生成合成图像的实例。
由 Progressive GAN 生成的虚构名人人脸图像(图源: NVIDIA)
基于 GAN 的面部动画生成(GANimation)
GANimation 是一种基于动作单元(AU)标注的新型 GAN 条件化方法,它在连续流形中描述了定义人脸表情解剖结构的运动。
GANimation 的官方实现(图源:GitHub)
绘画到照片的迁移
通过 GAN 使图像变得更真实的另一个例子是:直接地将一幅好画转换成一张照片。这是用一种叫做 CycleGAN 的特殊 GAN 做到的。CycleGAN 使用了两个生成器和两个判别器。我们将一个生成器称为 G,让它把图像从 X 域转换到 Y 域。将另一个生成器称为 F,它将图像从 Y 域转换到 x 域。每个生成器都有一个相应的判别器,该判别器试图将生成器合成的图像与真实图像区分开来。
CycleGAN 的生成结果(图源:GitHub)
我们将走向何方?
在不久的将来,机器学习和 GAN 必将对成像和摄影产生巨大的影响。目前,该技术能够根据文本输入生成简单的图像。然而,可以预见,未来它将不仅能够生成高分辨率的精确图像,还能够创建整段视频。想象一下,你只需将脚本输入 GAN 即可生成整部电影!不仅如此,每个人都可以使用简单的交互式APP来创建自己的电影(甚至可以由自己主演!)这种技术会是真正的摄影技术、导演和表演的末日吗?
酷炫的技术也意味着潜在的邪恶用途。人们还需要一种方法来识别和检测完美的假图像,需要对这类图像生成进行监管。目前,GAN 已经被用于制作假视频或「高仿作品」,这些假视频或「高仿作品」正被负面地使用,比如制作名人的假色情视频,或者在人们不知情的情况下以他们的形象发表言论。将音频和视频合成技术提供给普通大众的后果是可怕的。
人工图像生成技术是一柄双刃剑,尤其是在人们对它知之甚少的情况下。生成对抗网络是一个非常有用的工具,同时它也很危险。可以肯定的是,它将重塑技术世界,但是它将通过怎样的路径做到这一点,还有待思考。
原文链接:https://medium.com/sfu-big-data/ai-the-future-of-photography-c7c80baf993b