生成式對抗網路是無中生有的東西,請注意這不是產生出真實的東西!
比如說,你用了 100 倍的相機 拍了 10,000 張月球表面局部區域圖片,這些照片都是高解析的圖片。然後把這一萬張照片組合成一個更超高解析的月球全貌,組合出來的月球全貌絕對是無與倫比,但它是假的,而且是假到連專家也判斷不出來。
網路上的教學,都說 GAN 可以把模糊圖片變成高清晰、2D 可以變成 3D,是沒錯,因為連專家也判斷不出真假。但這不是還原,而是製作出一個人類無法判斷的假東西。
生成式對抗網路的特性就是讀入大量的圖片,然後產生幾可亂真的假圖。本篇使用 Mnist 的 60000 張真圖進行訓練。
模型組合
一開始若不把下面的模型組合搞清楚,程式碼就會看不懂。
首先產生 generator 模型,此模型輸入雜訊,經過權重計算,由 generator(noise) 產生 Fake images。
接下來建立 discriminator 鑑識模型,此模型的 Input 輸入資料就是 generator 的 Fake images ,再使用 discriminator.train_on_batch() 產生黃色端的 validity 資料。
請注意 discriminator.trainable = True,訓練時黃色的權重會被調整而提高辨識能力。但本程式沒有辨識的功能,而是直接告知這是真圖或是假圖,所以把 trainable 設為 False,以提高訓練效能。
combine 是 generator 及 discriminator 的結合體。此模型的 Input 會傳給 generator 的 Input,輸出 Fake images 後傳給 discriminator 的 Input, discriminator 的 validity 最後再傳給 combine 的 validity 。
那麼 generator 裏的權重要如何訓練呢? 就是由 combine.train_on_batch() 啟動訓練,讓 generator 產生的圖像愈來愈接近真實的狀況。
安裝套件
請先安裝如下套件
pip install tensorflow==2.10.1 matplotlib
程式運作流程
主程式中產生 gan=Gan() 物件。這個物件建構子會產生二個模型
generator 模型
此模型 input 層接收隨機產生的 100 個雜訊,用這個雜訊產生 256 => 512 => 1024 個資料,最後產生 784 個點形成圖片。
def build_generator(self): model = Sequential() model.add(Dense(256, input_dim=self.latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(1024)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(np.prod(self.img_shape), activation='tanh')) model.add(Reshape(self.img_shape)) model.summary() input = Input(shape=(self.latent_dim,)) img = model(input) return Model(input, img)
Discriminator [dɪˋskrɪmə͵netɚ] 模型 (鑑識器)
用來判斷產生的圖片與原圖的相似度,輸入層是 784 個相素的圖片,最後縮小成 512 => 256 => 1 個資料
def build_discriminator(self): model = Sequential() model.add(Flatten(input_shape=self.img_shape)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(256)) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(1, activation='sigmoid')) model.summary() input = Input(shape=self.img_shape) validity = model(input) return Model(input, validity)
combine
combine 由 generator 及 discriminator 二個模型組合成成
self.combine= Model(input, self.discriminator(self.generator(input))
請注意,要訓練 generator 的權重,不是由 generator.train() 訓練,而是由 combine.train_on_batch() 開始訓練。
train
最後再由主程式啟動 train 開始訓練。
模型下載
底下的完整代碼,訓練需要一段時間,沒顯卡會非常的久。所以無法訓練的人,請由本站下載 gan_mnist。本模型是本人訓練 10 萬次的結果。
完整代碼
本篇的完整代碼是參考 https://github.com/eriklindernoren/Keras-GAN/blob/master/gan/gan.py 修改而來的。
修改的部份為
1. 將模型獨立成一個類別
2. 訓練的指令放在主程式中
3. 一次讀入10000張圖片訓練
完整代碼如下
import os.path import shutil import numpy as np from keras import Sequential, Input, Model from keras.datasets import mnist from keras.layers import Dense, LeakyReLU, BatchNormalization, Flatten, Reshape from keras.optimizers import Adam import pylab as plt class Gan(): def __init__(self): self.img_rows = 28#寬 self.img_cols = 28#高 self.channels = 1 #單色 self.img_shape = (self.img_rows, self.img_cols, self.channels) self.latent_dim = 100#等會產生100個雜點 optimizer = Adam(0.0002, 0.5) # 建立 discriminator 模型 self.discriminator=self.build_discriminator() self.discriminator.compile( loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'] ) # 建立 generator 模型 self.generator=self.build_generator() input=Input(shape=(self.latent_dim)) img=self.generator(input)#產生輸出層,不是真正的圖 # discriminator 不訓練權重,因為本例不進行辨識 #而是真接告知圖片的真假 self.discriminator.trainable=False #self.discriminator.trainable = True output=self.discriminator(img)#產生輸出層,不是結果 self.combin= Model(input, output) self.combin.compile( loss="binary_crossentropy", optimizer=optimizer ) def build_generator(self): model=Sequential() model.add(Dense(256, input_dim=self.latent_dim)) #relu : 線性整流,將負值去除。LeakyReLU剛好相反,將值變成有負值 model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(1024)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) #np.prod([1,2,3]) = 1*2*3 = 6 model.add(Dense(np.prod(self.img_shape), activation='tanh')) model.add(Reshape(self.img_shape)) model.summary() input=Input(shape=(self.latent_dim)) output=model(input)#輸入input層,產生 output層 return Model(input, output) def build_discriminator(self): model = Sequential() #在第一層中,如果指定 input_shape, 則會在第一層前產生輸入層 model.add(Flatten(input_shape=self.img_shape))#產生了二層 model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(256)) model.add(LeakyReLU(alpha=0.2)) #sigmoid, 結果為 0 or 1,用於二分法 model.add(Dense(1, activation="sigmoid")) input=Input(shape=self.img_shape)#產生輸入層 model.summary() output=model(input)#產生輸出層 return Model(input, output) def sample_images(epoch): r, c = 5, 5 noise = np.random.normal(0,1,(r * c, latent_dim)) gen_imgs = gan.generator.predict(noise)#也可以直接寫成 self.generator(noise) #將顏色值還原成 0-255之間 gen_imgs = (0.5 * gen_imgs +0.5)*255 fig,axs=plt.subplots(r,c) idx=0 for i in range(r): for j in range(c): axs[i,j].imshow(gen_imgs[idx, :,:,0], cmap="gray") axs[i,j].axis("off") idx+=1 fig.savefig(f"images/{epoch}.png") plt.close() #設定圖檔儲存路徑 path="./images" if os.path.exists(path): shutil.rmtree(path) os.mkdir(path) batch_size=10000#每次讀入的圖片張數 epochs=10000#訓練的次數 latent_dim=100#潛在空間的雜訊數量 sample_interval=100 #設定多少 epoch 才儲存一次圖檔 #載入60000張圖片 (x_train, _), (_,_)=mnist.load_data() #產生模型 gan=Gan() #假圖標識為0, 真圖標識為 1 fake = np.zeros((batch_size, 1)) valid = np.ones((batch_size, 1)) #將顏色置於 -1 ~ 1 之間 x_train = x_train /127.5 -1. x_train = np.expand_dims(x_train, axis=3) for epoch in range(epochs): #取出 batch_size 個數字,每個數字介於 0~59999之間 idx=np.random.randint(0, x_train.shape[0], batch_size) real_imgs = x_train[idx] #產生 batch_size 組亂數, 每組有 100 個數 noise=np.random.normal(0,1,(batch_size, latent_dim)) #產生假的圖片,不是產生層 fake_imgs=gan.generator(noise) #開始訓練 model.fit(x, y) #底下會自動調整 discriminator 的權重 d_loss_real = gan.discriminator.train_on_batch(real_imgs, valid) d_loss_fake = gan.discriminator.train_on_batch(fake_imgs, fake) #底下只是為了顯示而以 d_loss = np.add(d_loss_real, d_loss_fake)/2.0 #訓練 generator #由 combin 去啟動 generator 的訓練,然後調整 generator 的權重 g_loss = gan.combin.train_on_batch(noise, valid) print(f"Epoch:{epoch:06d} D loss:{d_loss[0]:.6f}, acc:{100*d_loss[1]:.6f}%, G loss:{g_loss:.6f}") if epoch % sample_interval == 0: sample_images(epoch) #最後將 generator 的權重儲存 gan.generator.save("gan_mnist")
結果
一開始產生的照片如下
最後一次產生的照片如下
這種無中生有的東西竟然會發生在這世界上,人類想當上帝真的是想瘋了。
載入 generator 模型直接產生新圖
前面的代碼中,我們將 generator 的權重儲存在 gan_mnist 目錄中。所以下次不想重新訓練,而是想直接產生新圖時,就可以使用如下代碼
from keras import models import numpy as np import pylab as plt generator=models.load_model("gan_mnist") r, c = 5, 5 np.random.seed(10) noise = np.random.normal(0, 1, (r * c, 100)) gen_imgs = generator.predict(noise) # 也可以直接寫成 self.generator(noise) # 將值改為 0-1之間 gen_imgs = (0.5 * gen_imgs + 0.5)*255 fig, axs = plt.subplots(r, c) idx = 0 for i in range(r): for j in range(c): axs[i, j].imshow(gen_imgs[idx, :, :, 0], cmap="gray") axs[i, j].axis("off") idx += 1 plt.show()