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