GAN,生成式對抗網絡(Generative Adversarial Networks)是一種深度學習模型,是近幾年來復雜分布上無監督學習最具前景的方法之一。
機器學習的模型可大體分為兩類,生成模型(Generative model)和判別模型(Discriminator model),判別模型需要輸入變量,通過某種模型來預測,生成模型是給定某種隱含信息,來隨機產生數據。
GAN主要包括了兩個部分,即生成器generator與判別器discriminator。生成器主要用來學習真實圖像分布從而讓自身生成的圖像更加的真實,以騙過判別器。判別器則需要對接受的圖片進行真假判別。
原理:
在訓練過程中,生成器努力地讓生成的圖像更加真實,而判別器則努力地去識別圖像的真假,這個過程相當於二人進行博弈,隨着時間的推移,生成器和判別器在不斷地進行對抗。最終兩個網絡達到了一個動態平衡,生成器生成的圖像接近於真實圖像分布,而判別器識別不出真假圖像,對於給定圖像的預測為真的概率基本接近0.5(相當於隨機猜測類別)。
最終的結果:
G可以生成足以'以假亂真'的圖片G(z),對於D來說,它難以判定G生成的圖片究竟是不是真實的,因此D(G(z))=0.5,目的就是為了得到一個生成模型G,可以用來生成圖片。
GAN的應用領域:
- 圖像生成
- 圖像增強
- 風格化
- 藝術的圖像創造
GAN的簡單實現
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers import matplotlib.pyplot as plt import numpy as np import glob import os (train_images,train_labels),(_,_) = tf.keras.datasets.mnist.load_data() train_images = train_images.reshape(train_images.shape[0],28,28,1) train_images = tf.cast(train_images,tf.float32) train_images = (train_images - 127.5) / 127.5 BATCH_SIZE = 256 #None就是每個BATCH BUFFER_SIZE = 60000 datasets = tf.data.Dataset.from_tensor_slices(train_images) print(datasets) #<TensorSliceDataset shapes: (28, 28), types: tf.float32> datasets = datasets.shuffle(BUFFER_SIZE).batch(BATCH_SIZE) def generator_model(): model = tf.keras.Sequential() model.add(layers.Dense(256,input_shape=(100,),use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Dense(512,use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Dense(28*28*1,use_bias='False',activation='tanh')) model.add(layers.BatchNormalization()) model.add(layers.Reshape((28,28,1))) return model def discriminator_model(): model = tf.keras.Sequential() model.add(layers.Flatten()) model.add(layers.Dense(512,use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Dense(256,use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) #先不用進行激活,可以利用損失函數 model.add(layers.Dense(1)) return model cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits='True') #告訴損失函數我們最后一層沒有激活 def discriminator_loss(real_out,fake_out): #這里希望讓真實圖像被判定為1,假的圖像判定為0 real_loss = cross_entropy(tf.ones_like(real_out),real_out) fake_loss = cross_entropy(tf.zeros_like(fake_out),fake_out) return real_loss + fake_loss #生成器接收假的的圖片並希望能夠被判為真 def generator_loss(fake_out): return cross_entropy(tf.ones_like(fake_out),fake_out) #創建生成器優化器 generator_optimizer = tf.keras.optimizers.Adam(1e-4) #創建判別器優化器 discriminator_optimizer = tf.keras.optimizers.Adam(1e-4) generator = generator_model() discriminator = discriminator_model() EOPCHS = 10 NOISE_DIM = 100 num_ex_to_generate = 16 #生成16個長度為100的向量 seed = tf.random.normal([num_ex_to_generate,NOISE_DIM]) def train_step(images): noise = tf.random.normal([BATCH_SIZE,NOISE_DIM]) with tf.GradientTape() as gen_tape,tf.GradientTape() as disc_tape: real_out = discriminator(images,training=True) gen_image = generator(noise,training=True) fake_out = discriminator(gen_image,training=True) gen_loss = generator_loss(fake_out) disc_loss = discriminator_loss(real_out,fake_out) gradient_gen = gen_tape.gradient(gen_loss,generator.trainable_variables) gradient_disc = disc_tape.gradient(disc_loss,discriminator.trainable_variables) generator_optimizer.apply_gradients(zip(gradient_gen,generator.trainable_variables)) discriminator_optimizer.apply_gradients(zip(gradient_disc,discriminator.trainable_variables)) #畫出這一個批次的圖片 def generate_plot_image(gen_model,test_noise): pre_images = gen_model(test_noise,training=False) fig = plt.figure(figsize=(4,4)) for i in range(pre_images.shape[0]): plt.subplot(4,4,i+1) #因為在生成圖片最后用激活函數tanh把圖片映射到[-1,1]之間,現在要把取值范圍改變成[0,1]的范圍 plt.imshow(pre_images[i,:,:,0] + 1/ 2,cmap='gray') plt.axis('off') plt.show() def train(dataset,epochs): for epoch in range(epochs): print('Epoch',epoch + 1,':') for image_path in dataset: train_step(image_path) print('.',end='') generate_plot_image(generator,seed) print('\n') train(datasets,EOPCHS)
DCGAN
DCGAN就是將CNN和原始的GAN結合到了一起,生成的模型和判別模型都運用了深度卷積神經網絡的生成對抗網絡。這個網絡架構由<<Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks>>這篇論文提出,DCGAN對卷積神經網絡的結構做了一些改變,以提高樣本的質量和收斂的速度。
DCGAN的主要設計技巧:
1.取消所用pooling層。生成器網絡中使用轉置卷積(transposed convolution layer)進行上采樣,判別器網絡中加入stride的卷積代替pooling。
2.去掉全連接層(FC),使網絡變成全卷積網絡。
3.生成器網絡中使用Relu作為激活函數,最后一層使用tanh。
4.判別器網絡中使用LeakyRelu作為激活函數。
5.在Generator和Discriminator上都使用BatchNorm解決初始化差的問題,幫助梯度傳播到每一層,防止Generator把所有樣本都收斂到同一個點,直接將BatchNorm應用到所有層會導致樣本震盪和模型不穩定,通過在Generator輸出層和Discriminator輸入層不采用BatchNorm可以防止這種現象。
6.使用Adam優化器,beta1(1階矩估計的指數衰弱率)的值設置為0.5
7.論文參數
LeakyReLU的參數設置為0.2、learning rate=0.002、batch size=128
def generator_model(): model = tf.keras.Sequential() model.add(layers.Dense(7*7*256,input_shape=(100,),use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Reshape((7,7,256))) model.add(layers.Conv2DTranspose(128,(5,5),strides=(1,1),padding='same',use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Conv2DTranspose(64,(5,5),strides=(2,2),padding='same',use_bias=False)) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Conv2DTranspose(1,(5,5),strides=(2,2),padding='same',use_bias=False,activation='tanh')) return model def discriminator_model(): model = tf.keras.Sequential() model.add(layers.Conv2D(64,(5,5),strides=(2,2),padding='same',input_shape=(28,28,1))) model.add(layers.LeakyReLU()) model.add(layers.Dropout(0.3)) model.add(layers.Conv2D(128,(5,5),strides=(2,2),padding='same')) model.add(layers.LeakyReLU()) model.add(layers.Dropout(0.3)) model.add(layers.Conv2D(256,(5,5),strides=(2,2),padding='same')) model.add(layers.LeakyReLU()) model.add(layers.Flatten()) model.add(layers.Dense(1)) return model
cGAN
原始GAN的缺點
生成的圖像是隨機的,不可預測的,無法控制網絡輸出特定的圖片,生成目標不明確,可控性不強,針對原始GAN不能生成具有特定屬性的圖片的問題,Mechi Mirza等人提出了cGAN,其核心在於將屬性信息y融入生成器G和判別器D中,其核心在於將屬性y可以是任何標簽信息,例如圖像的類別、人臉圖像的面部表情等。
把無監督學習轉化為有監督學習。
cGAN網路架構
cGAN缺陷
cGAN生成雖有很多缺陷,譬如圖像邊緣模糊,生成的圖像分辨率太低等,但是它為后面的Cycle-GAN開拓了道路,這兩個模型轉換圖像的風格時對屬性特征的處理方法均受cGAN的啟發。
ACGAN
CGAN通過在生成器和判別器中均使用標簽信息進行訓練,不僅能產生特定標簽的數據,ACGAN是條件GAN的另一種實現,既使用標簽信息進行訓練,同時也重建標簽信息。
生成器的輸入包含class與noise兩個部分,其中class為訓練數據標簽信息,noise為隨機向量,然后將兩者進行拼接,生成的輸出張量為圖片,(batch_size, channel, height, width)。
判別器的輸出為圖片(生成圖片和真實圖片),判別器的輸出為兩個部分,一部分是源數據真假的判斷,形狀為:(batch_size,1),一部分是輸入數據的分類結果,形狀為( batch_size, class_num)
判別器的輸出部分,因此判別器的最后一層有兩個並列的全連接層,分別得到這兩部分的輸出結果,即判別器的輸出有兩個張量(真假判斷張量和分類結果張量)。
損失函數
對於判別器而言,既希望分類正確,又希望能正確分辨數據的真假,對於生成器而言,也希望能夠分類正確,但希望判別器不能正確分類假數據。
InfoGAN
在CGAN中,生成器網絡還有一個附加參數c,即G(z,c)其中c是一個條件變量,在CGAN中,c假設在語義上是已知的,例如標簽,因此在訓練期間我們必須提供它,在InfoGAN中,我們假設c不知道,所以我們做的是為c提出一個先驗,並根據數據推斷它,即我們想找到后驗p(c|x,z)。
InfoGAN所要表達的目標就是通過非監督學習得到可分解的特征表示,使用GAN加上最大化生成的圖片和編碼之間的交互信息。最大的好處就是可以不需要監督學習,而且不需要大量額外的計算花銷就能得到可解釋的特征。
InfoGAN的出發點,它試圖利用z,尋找一個可解釋的表達,於是它將z進行了拆解,一是噪聲z,二是可解釋的隱變量c,而我們希望通過約束c與生成數據之間的關系,可以使得c里面包含有對數據的可解釋信息。
對於MNIST數據,c可以分為categorical latent code代表數字的種類信息(0-9),以及continuous latent code來表示傾斜度、筆畫粗細等。
這些特征在數據空間中以一種復雜的無序方式進行編碼,但是如果這些特征是可分解的,那么這些特征將具有更強的可解釋性,我們將更容易的利用這些特征進行編碼,所以,我們將如何通過非監督學習方式獲取這些可分解的特征呢?InfoGAN通過使用連續的和離散的隱含因子來學習可分解的特征。
如果從表征學習來看GAN模型,由於在生成器使用噪聲z的時候沒有加任何的限制,所以在以一種高度混亂的方式使用z,z的任何一個維度都沒有明顯的表示一個特征,所以在數據生成的過程,我們無法得知什么樣的噪聲z可以用來生成數字1,什么樣的噪聲z可以用來生成數字3,這一點限制了我們對GAN的使用。在生成器中除了原先的噪聲z還增加了一個隱含編碼c,所謂InfoGAN,其中Info代表互信息,它表示生成數據x與隱藏編碼c之間關聯程度的大小,為了使x與c之間關系密切,所以我們需要最大化互信息的值,據此對原始GAN模型的值做了一些修改,相當於加了一個互信息的正則化項。
什么是互信息?我們可以把互信息看成當觀測到y值而造成的x的不確定性的減小,如果x,y是相互獨立的沒有相關性,即互信息的值為0,那么已知y值的情況下推測x與x的原始分布沒有區別;如果x,y有相關性,即互信息的值大於0,那么在已知y的情況下,我們就能知道哪些x的值出現的概率更大。
在訓練期間,我們可以任意分配一個先驗c給一張圖片,實際上,我們可以根據需要添加任意數量的先驗,InfoGAN可能會為它們分配不同的屬性,InfoGAN的作者將其稱為"解開的表示",因為它將數據的屬性分解為幾個條件參數。
InfoGAN設計
判別器網絡D(x)和生成器網絡G(z,c)的訓練過程與CGAN非常相似,差異是:代替D(x,c),我們在InfoGAN中使用判別器GAN:D(x),即無條件判別器,對於生成器網絡,我們給予了一個觀察數據(或者說條件)c,即G(z,c)。
除了D(x),G(z,c),我們需要在訓練一個網絡Q(c|x)這樣我們就能計算互信息。Q也可以視作一個判別器,輸出類別c。
pix2pix GAN
主要用於圖像轉換,又稱為圖像翻譯。普通的GAN接收的G部分是隨機向量,輸出的是圖像,D部分接收的輸入是圖像,生成的或是真實的,輸出是對或者錯,這樣G和D就能聯手輸出真實的圖像。
對於圖像翻譯任務來說,它的G輸入顯然應該是一張圖x,輸出當然也是一張圖y,不需要添加隨機輸入。對於圖像翻譯任務而言,輸入與輸出之間會共享很多的信息,比如輪廓信息是共享的。如果使用普通的卷積神經網絡,那么會導致每一層都承載保存着所有的信息,這樣神經網絡很容易出錯。
U-net是變形的Encoder-Decoder模型,它將第i層拼接到第n-i層,這樣做是因為第i層和第n-i層的圖像大小是一致的,可以認為他們承載着類似的信息。但是D的輸入卻發生了一些變化,因為除了要生成真實圖像之外,還要保證生成的圖像是匹配的,於是D的輸入就做了一些變動,D中要輸入成對的圖像,類似於CGAN。Pix2Pix中的D在論文中被實現為Patch-D,所謂的Patch,是指無論生成的圖像有多大,將其切分為多個固定大小的Patch輸入進入D去判斷。這樣設計的好處在於D的輸入變小,計算量變小,訓練速度快。
損失函數
D網絡損失函數:
1.輸入真實的成對圖像希望判定為1
2.輸入生成與原圖像希望判定為0
G網絡損失函數:
1.輸入生成圖像與原圖像希望判定為1
對於圖像翻譯任務,G的輸入和輸出之間其實共享了很多信息,比如圖像上色的任務,輸入和輸出之間就共享了邊緣信息,因此為了保證輸入圖像和輸出圖像之間的相似度還添加了L1損失。
論文要點
1.類似CGAN,輸入為圖像而不是隨機向量。
2.使用U-net,使用跳階來共享更多的信息。
3.輸入成對的圖像到D保證映射。
4.使用Patch-D來降低計算量提升效果。
5.L1損失函數的加入來保證輸入和輸出之間的一致性。
Cycle GAN
主要用於圖像之間的轉換,如圖像風格轉換。Cycle GAN適用於非配對的圖像到圖像風格的轉換,它解決了需要成對數據進行訓練的困難。
原理:將一類圖片轉化為另一類圖片,即現在有兩個樣本空間,X和Y,我們希望把X空間中的樣本轉換成Y空間的樣本(獲取一個數據集的特征,並轉換為另一個數據集特征)。我們的實際目標就是學習從X到Y空間的映射,設這個映射為F,它就對應GAN中的生成器,F可以將X中的圖片x轉換為Y中的圖片F(x)。對於生成的圖片,我們需要GAN的判別器來判斷它是否為真實圖片,由此構成對抗生成式網絡。從理論上來講,對抗訓練可以學習和產生與目標域Y相同分布的輸出,但會產生一些問題。
單向Cycle GAN
在足夠大的樣本容量下,網絡可以將相同的輸入圖像集合映射到目標域中圖像的任何隨機排列,其中任何學習的映射可以歸納出與目標分布匹配的輸出分布,即映射F完全可以將所有x都映射為Y空間的同一張圖片,是損失無效化。因此,單獨的對抗損失Loss不能保證學習函數可以將單個輸入Xi映射到期望的輸出Yi,對此作者又提出了"循環一致性損失",cycle consistency loss。
我們希望能夠把domain A的圖片(命名為圖a)轉化為domain B的圖片(命名為圖b),為了實現這個過程,我們需要生成器G_AB和G_BA,分別把domain A和domain B的圖片進行互相轉換。將X的圖片轉換到Y空間后,應該還可以在轉換回來,這樣就能杜絕模型把所有X的圖片轉換為Y空間的同一張圖片了。
最后為了訓練這個單向GAN需要兩個loss,分別是生成器的重建loss和判別器的判別loss。
1.判別loss D_B使用來判斷輸入的圖片是否為真實的domain B的圖片。
2.生成loss:生成器用來重建圖片a,目的是希望生成的圖片G_BA(G_AB(a))和原圖a盡可能的相似,那么可以很簡單的采取L1 loss或者L2 loss,最后生成loss表示為:
Cycle GAN其實就是一個A->B單向GAN加上一個B->A單向GAN,兩個GAN共享兩個生成器,然后自帶一個判別器,所以加起來總共有兩個判別器和兩個生成器。一個單向GAN有兩個loss,而Cycle GAN加起來有四個loss。
局限性:
對於顏色、紋理等的轉換效果比較好,對多樣性高的、多變的效果轉換不好(如幾何轉換)。
SSGAN
SSGAN是一個半監督學習生成對抗網絡,初衷是利用GAN生成器生成的樣本來改進和提高圖像分類任務的性能。
SSGAN的主要設計思想在鑒別器的設計,我們希望設計的鑒別器扮演執行圖像分類任務的分類器的角色,又能區分由生成器生成的生成樣本和真實數據。
對於包含N個類別的數據集,真實的圖像將被分類到N個類別中,生成的圖像將被分入第N+1類中。
損失函數:
1.鑒別器損失函數
2.生成器損失函數
SSGAN實現中最關鍵的是損失函數的構建:鑒別器引入huber loss,就是L2損失,目的是生成器生成的圖像與真實輸入圖像的損失越小越好。