生成式對抗網絡(GAN,generative adversarial network)由Goodfellow等人於2014年提出,它可以替代VAE來學習圖像的潛在空間。它能夠迫使生成圖像與真實圖像在統計上幾乎無法區別,從而生成相當逼真的合成圖像。
1.GAN是什么?
簡單來說就是由兩部分組成,生成器generator網絡和判別器discriminator網絡。一部分不斷進化,使其對立部分也不斷進化,實現共同進化的過程。
對GAN的一種直觀理解是,想象我們想要試圖生成一個二次元頭像。一開始,我們並不擅長這項任務,就將自己的一些噪音二次元頭像和真的二次元頭像混在一起,並將其展示給discriminator。discriminator對每個頭像進行真實性評估,並向我們給出反饋,告訴我們是什么讓二次元頭像看起來像真的二次元頭像,我們回到自己的工作室,並准備一些新的二次元頭像。隨着時間的推移,我們變得越來越擅長模仿二次元頭像的風格,discriminator也變得越來越擅長找出假的二次元頭像。最后,我們手上擁有了一些優秀的二次元頭像。
2.為什么?
【1】為什么我們有真的二次元頭像和假的二次元頭像,為什么不自己用監督學習生成新的二次元頭像呢?
generator無法自己獨立學習的原因是,以vae為例,輸出layer層輸出的是各像素點,而他們在輸出時是獨立的,沒有相互作用的,因此無法判斷總體的效果進行自主學習。對於discriminator,其輸入是生成的整張圖像,因此可以從總體上進行判斷。
需要注意的是,discriminator對於輸入的真實圖像都應是高分,那么如果訓練時只給它真實圖像的話,他就無法實現正確的判斷,會將所有輸入都判為高分。所以需要一些差的圖像送給discriminator進行訓練,並且這些差的圖像不應是簡單的加些噪聲之類的能讓它輕易分辨的。因此,訓練它的方法是,除真實圖像外先給它一些隨機生成的差的例子,然后對discriminator解argmaxD(x)做generation生成出一些他覺得好的圖像,然后將原本極差的圖像換為這些圖像再進行訓練,如此往復,discriminator會不斷產生更好的圖像,將這些作為negative examples給其學習,達到訓練的目的。
【2】discriminator對真的二次元頭像這么了解,為什么他不自己做,而是要來指導我們做呢?
那既然如此,為什么還需要generator呢?discriminator自己也可以生成圖像啊?
這是因為discriminator生成圖像需要解argmaxD(x), 難度較大,一般需要假設一些條件才會好解,比如網絡假設為線性時,但這樣會限制圖像的生成效果。而generator生成非常快,因此將二者結合起來共同學習實現輸出好的結果。二者優缺點如下所示:
總而言之,因為generator沒有全局觀,所以需要結合discriminator學習,對於discriminator,使用generator生成圖像比自己解方程生成更簡單高效,這二者的優缺點相互補充。
GAN的目的是為了生成,而VAE目的是為了壓縮,目的不同效果自然不同。比如,由於二范數的原因,VAE的生成是模糊的。而GAN的生成是犀利的。
數據集為CIFAR10,包含50000張32*32的RGB圖像,這些圖像屬於10個類別(每個類別5000張圖像),這里我們只使用屬於“frog”(青蛙)類別的圖像
import keras from keras import layers import numpy as np
|
|
生成器網絡:將一個向量(來自潛在空間,訓練過程中對其隨機采樣)轉換為一張候選圖像 生成器從未直接見過訓練集中的圖像,它所知道的關於數據的信息都來自於判別器。 |
|
latent_dim = 32 height = 32 width = 32 channels = 3 generator_input = keras.Input(shape=(latent_dim,)) #將輸入轉換為大小為16*16的128個通道的特征圖 x = layers.Dense(128*16*16)(generator_input) x = layers.LeakyReLU()(x) x = layers.Reshape((16,16,128))(x) x = layers.Conv2D(256,5,padding='same')(x) x = layers.LeakyReLU()(x) #上采樣為32*32 x = layers.Conv2DTranspose(256,4,strides=2,padding='same')(x) x = layers.LeakyReLU()(x) x = layers.Conv2D(256,5,padding='same')(x) x = layers.LeakyReLU()(x) x = layers.Conv2D(256,5,padding='same')(x) x = layers.LeakyReLU()(x) #生成一個大小為32*32的單通道特征圖(即CIFAR10圖像的形狀) x = layers.Conv2D(channels,7,activation='tanh',padding='same')(x) #將生成器模型實例化,它將形狀為(latent_dim,)的輸入映射到形狀為(32,32,3)的圖像 generator = keras.models.Model(generator_input,x) generator.summary()
|
|
判別器網絡:它接收一張候選圖像(真實的或合成的)作為輸入,並將其划分到這兩個類別之一:"生成圖像"或"來自訓練集的真實圖像" |
|
#GAN判別器網絡 discriminator_input = layers.Input(shape=(height,width,channels)) x = layers.Conv2D(128,3)(discriminator_input) x = layers.LeakyReLU()(x) x = layers.Conv2D(128,4,strides=2)(x) x = layers.LeakyReLU()(x) x = layers.Conv2D(128,4,strides=2)(x) x = layers.LeakyReLU()(x) x = layers.Conv2D(128,4,strides=2)(x) x = layers.LeakyReLU()(x) x = layers.Flatten()(x) x = layers.Dropout(0.4)(x) x = layers.Dense(1,activation='sigmoid')(x)#分類層 #將判別器模型實例化,它將形狀為(32,32,3)的輸入轉換為一個二進制分類決策(真/假) discriminator = keras.models.Model(discriminator_input,x) discriminator.summary()
|
|
discriminator_optimizer = keras.optimizers.RMSprop( lr=0.0008, clipvalue = 1.0, #在優化器中使用梯度裁剪(限制梯度值的范圍) decay = 1e-8,#為了穩定訓練過程,使用學習率衰減 ) discriminator.compile(optimizer=discriminator_optimizer, loss='binary_crossentropy')
|
設置GAN,將生成器和判別器連接在一起 訓練時,這個模型將讓生成器向某個方向移動,從而提高它欺騙判別器的能力。這個模型將潛在空間的點轉換為一個分類決策(即"真"或"假") 它訓練的標簽都是"真實圖像"。因此,訓練gan將會更新generator得到權重,使得discriminator在觀測假圖像時更有可能預測為"真"。 |
對抗網絡 |
|
discriminator.trainable = True #將判別器權重設置為不可訓練(僅應用於gan模型) gan_input = keras.Input(shape=(latent_dim,)) gan_output = discriminator(generator(gan_input)) gan = keras.models.Model(gan_input,gan_output) gan_optimizer = keras.optimizers.RMSprop(lr=0.0004,clipvalue=1.0,decay=1e-8) gan.compile(optimizer=gan_optimizer,loss='binary_crossentropy')
|
注意:在訓練過程中需要將判別器設置為凍結(即不可訓練),這樣在訓練gan時它的權重才不會更新。 如果在此過程中可以對判別器的權重進行更新,那么我們就是在訓練判別器始終預測"真",但這並不是我們想要的。 |
實現GAN的訓練 |
|
import os from keras.preprocessing import image (x_train,y_train),(_,_) = keras.datasets.cifar10.load_data() #cifar數據集 x_train = x_train[y_train.flatten() == 6]#選擇青蛙的圖像 x_train = x_train.reshape((x_train.shape[0],) + (height,width,channels)).astype('float32')/255.
|
|
iterations = 1000 batch_size = 2 save_dir = 'frog_dir' start = 0 for step in range(iterations): random_latent_vectors = np.random.normal(size=(batch_size,latent_dim)) generated_images = generator.predict(random_latent_vectors)#點-->虛假圖像 stop = start + batch_size #混淆真實圖像和虛假圖像 real_images = x_train[start:stop] combined_images = np.concatenate([generated_images, real_images]) labels = np.concatenate([np.ones((batch_size,1)), np.zeros((batch_size,1))]) labels += 0.05 * np.random.random(labels.shape) #向標簽中添加噪聲 #訓練判別器 d_loss = discriminator.train_on_batch(combined_images,labels) #在潛在空間中采樣隨機點 random_latent_vectors = np.random.normal(size=(batch_size,latent_dim)) #合並標簽,全都是“真實圖像”(這是在撒謊) misleading_targets = np.zeros((batch_size,1)) #通過gan模型來訓練生成器(此時凍結判別器模型) a_loss = gan.train_on_batch(random_latent_vectors,misleading_targets) start += batch_size if start > len(x_train) - batch_size: start = 0 if step % 2 == 0: gan.save_weights('gan.h5') print('discriminator loss:',d_loss) print('adversarial loss:',a_loss) img = image.array_to_img(generated_images[0] * 255.,scale=False) img.save(os.path.join(save_dir,'generated_frog'+str(step)+'.png')) img = image.array_to_img(real_images[0]*255.,scale=False) img.save(os.path.join(save_dir,'real_frog'+str(step)+'.png'))
|
判別器損失:d_loss=(生成的圖像和真實圖像->標簽)
gan損失:a_loss=(隨機采樣的點->全是'真'的標簽)
第一次
最后一次 |