GAN-生成手寫數字-Keras


from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers.core import Activation
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import UpSampling2D
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Flatten
from keras.optimizers import SGD
from keras.datasets import mnist
import numpy as np
from PIL import Image
import argparse
import math

一、首先要定義一個生成器G,該生成器需要將輸入的隨機噪聲變換為圖像。

1. 該模型首先輸入有100個元素的向量,該向量隨機生成於某分布。

2. 隨后利用兩個全連接層接連將該輸入向量擴展到1024維和128 * 7 * 7

3. 后面就開始將全連接層所產生的一維張量重新塑造成二維張量,即MNIST中的灰度圖

4. 由全連接傳遞的數據會經過幾個上采樣層和卷積層,注意到最后一個卷積層所采用的卷積核為1,所以經過最后卷積層所生成的圖像是一張二維灰度圖

def generator_model():
    # 下面搭建生成器的架構,首先導入序貫模型(sequential),即多個網絡層的線性堆疊
    model = Sequential()
    # 添加一個全連接層,輸入為100維向量,輸出1024維
    model.add(Dense(input_dim=100, output_dim=1024))
    # 添加一個激活函數tanh
    model.add(Activation('tanh'))
    # 添加一個全連接層,輸出為 128 * 7 * 7維度
    model.add(Dense(128*7*7))
    # 添加一個批量歸一化層,該層在每個batch上將前一層的激活值重新規范化,即使得其輸出數據的均值接近0,其標准差接近1
    model.add(BatchNormalization())
    model.add(Activation('tanh'))
    
    # Reshape層用來將輸入shape轉換為特定的shape,將含有 128*7*7 個元素的向量轉換為 7*7*128 張量
    model.add(Reshape((7, 7, 128), input_shape=(128*7*7,)))
    # 2維上采樣層,即將數據的行和列分別重復2次
    model.add(UpSampling2D(size=(2, 2)))
    # 添加一個2維卷積層,卷積核大小為5X5,激活函數為tanh,共64個卷積核,並采用padding以保持圖像尺寸不變
    model.add(Conv2D(64, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    model.add(UpSampling2D(size=(2, 2)))
    
    # 卷積核設為1即輸出圖像的維度
    model.add(Conv2D(1, (5, 5), padding='same'))
    model.add(Activation('tanh'))
    return model

二、判別模型

判別模型就是比較傳統的圖像識別模型,可以按照經典的方法采用幾個卷積層與最大池化層,而后再展開為一維張量並采用幾個全連接層作為架構

def discrimiator_model():
    # 下面搭建判別器架構,同樣采用序貫模型
    model = Sequential()
    
    # 添加一個2維卷積層,卷積核大小為5X5,激活函數為tanh,輸入shape在 'channel_first' 模式下為 (samples, channels, rows, cols)
    # 在 ‘channel_last’模式下為 (samples, rows, cols, channels),輸出為64維。 元素的順序發生了一定的改變
    model.add(Conv2D(64, (5, 5),
                     padding='same',
                    input_shape=(28, 28, 1))
             )
    model.add(Activation('tanh'))
    
    # 為空域信號施加最大值池化,pool_size 取(2, 2)代表使圖片在兩個維度均變為原長的一半
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(128, (5, 5)))
    model.add(Activation('tanh'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    
    # Flatten層把多維輸入一維化,常用在卷積層到全連接層的過渡
    model.add(Flatten())
    model.add(Dense(1024))
    model.add(Activation('tanh'))
              
    # 一個結點進行二值分類,並采用sigmoid函數的輸出作為概念
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    return model

三、模型拼接

我們在訓練生成模型時,需要固定判別模型D以極小化價值函數而尋求更好的生成模型,這就意味着我們需要將生成模型與判別模型拼接在一起,並固定D的權重以訓練G的權重。因此訓練這個組合模型才能真正更新生成模型的參數。

def generator_containing_discriminator(g, d):
    # 將前面定義的生成器架構和判別器架構拼接成一個大的神經網絡,用於判別生成的圖片
    model = Sequential()
    # 先添加生成器架構,再令d不可訓練,即固定d
    # 因此在給定d的情況下訓練生成器,即通過將生成的結果投入到判別器進行辨別而優化生成器
    model.add(g)
    d.trainable = False
    model.add(d)
    return model

四、生成圖片拼接

# 生成圖片拼接
def combine_images(generated_images):
    num = generated_images.shape[0]
    width = int(math.sqrt(num))
    height = int(math.ceil(float(num)/width))
    shape = generated_images.shape[1:3]
    image = np.zeros((height*shape[0], width*shape[1]),
                    dtype=generated_images.dtype)
    
    for index, img in enumerate(generated_images):
        i = int(index / width)
        j = index % width
        image[i * shape[0] : (i + 1) * shape[0], j * shape[1] : (j + 1) * shape[1]] = img[:, :, 0]
    return image

五、訓練

1. 加載MNIST數據

2. 將數據分割為訓練與測試集,並賦值給變量

3. 設置訓練模型的超參數

4. 編譯模型的訓練過程

5. 在每一次迭代內,抽取生成圖像與真實圖像,並打上標注

6. 隨后將數據投入到判別模型中,並進行訓練與計算損失

7. 固定判別模型,訓練生成模型並計算損失,結束這一次迭代

def train(BATCH_SIZE):
    # 加載數據,將數據集下載到本地‘/.keras/datasers/’
    # 下載地址:https://s3.amazonaws.com/img-datasets/mnist.npz
    (X_train, y_train), (X_test, y_test) = mnist.load_data(r'C:/Users/Administrator/.keras/datasets/mnist.npz')
    # image_data_format選擇‘channels_last’或‘channels_first’,該選項指定了Keras將要將要使用的維度順序
    # ‘channels_first’假定2D數據的維度順序為(channels, rows, cols), 3D數據的維度順序為(channels, conv_dim1, conv_dim2, conv_dim3)
    
    # 轉換字段類型,並將數據導入變量中
    X_train = (X_train.astype(np.float32) - 127.5) / 127.5
    X_train = X_train[:, :, :, None]
    X_test = X_test[:, :, :, None]
    
    # 將定義好的模型架構賦值給特定的變量
    d = discrimiator_model()
    g = generator_model()
    d_on_g = generator_containing_discriminator(g, d)
    
    # 定義生成器模型、判別器模型,更新所使用的優化算法及超參數
    d_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)
    g_optim = SGD(lr=0.001, momentum=0.9, nesterov=True)
    
    # 編譯三個神經網絡並設置損失函數和優化算法,其中損失函數都是用二元分類交叉熵函數。編譯是用來配置模型學習過程的
    g.compile(loss='binary_crossentropy', optimizer='SGD')
    d_on_g.compile(loss='binary_crossentropy', optimizer=g_optim)
    
    # 前一個架構在固定判別器的情況下訓練了生成器,所以在訓練判別器之前先要設定其為可訓練
    d.trainable = True
    d.compile(loss='binary_crossentropy', optimizer=d_optim)
    
    # 下面在滿足epoch條件下進行訓練
    for epoch in range(10):
        print("Epoch is", epoch)
        
        # 計算一個epoch所需要的迭代數量,即訓練樣本數除批量大小數的值取整,其中shape[0]就是讀取矩陣第一維度的長度
        print("Number of batches", int(X_train.shape[0] / BATCH_SIZE))
        
        # 在一個epoch內進行迭代訓練
        for index in range(int(X_train.shape[0] / BATCH_SIZE)):
            # 隨機生成的噪聲服從均勻分布,且采樣下屆為-1,采樣上屆為1, 輸出BATCH_SIZE * 100個樣本,即抽取一個批量的隨機樣本
            noise = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))
            
            # 抽取一個批量的真實圖片
            image_batch = X_train[index * BATCH_SIZE : (index + 1) * BATCH_SIZE]
            
            # 生成的圖片使用生成器對隨機噪聲進行推斷,verbose為日志顯示
            # 0為不在標准輸出流輸出日志信息,1為輸出進度條記錄
            generated_images = g.predict(noise, verbose=0)
            
            # 每經過100次迭代輸出一張生成的圖片
            if index % 100 == 0:
                image = combine_images(generated_images)
                image = image * 127.5 + 127.5
                Image.fromarray(image.astype(np.uint8)).save("C:/Users/Administrator/GAN/" + str(epoch) + "_" + str(index) + ".png")
                
            # 將真實圖片和生成圖片以多維數組的形式拼接在一起,真實圖片在上,生成圖片在下
            X = np.concatenate((image_batch, generated_images))
            
            # 生成圖片真假標簽,即一個包含兩倍批量大小的列表
            # 前一個批量大小都是1,代表真實圖片,后一個批量大小都是0,代表偽造圖片
            y = [1] * BATCH_SIZE + [0] * BATCH_SIZE
            
            # 判別器的損失,在一個batch的數據上進行一次參數更新
            d_loss = d.train_on_batch(X, y)
            print("batch %d d_loss : %f" % (index, d_loss))
            
            # 隨機生成的噪聲服從均勻分布
            noise = np.random.uniform(-1, 1, (BATCH_SIZE, 100))
            
            # 固定判別器
            d.trainable = False
            
            # 計算生成器損失,在一個batch的數據上進行一次參數更新
            g_loss = d_on_g.train_on_batch(noise, [1] * BATCH_SIZE)
            
            # 令判別器可訓練
            d.trainable = True
            print("batch %d g_loss : %f" % (index, g_loss))
            
            # 每100次迭代保存一次生成器和判別器的權重
            if index % 100 == 0:
                g.save_weights('generator', True)
                d.save_weights('discrimiator', True)
train(32)

六、運行生成好的模型生成圖片

# 訓練完模型后,可以運行該函數生成圖片
def generate(BATCH_SIZE, nice = False):
    g = generator_model()
    g.compile(loss='binary_crossentropy', optimizer='SGD')
    g.load_weights('generator')
    
    if nice:
        d = discrimiator_model()
        d.compile(loss='binary_crossentropy', optimizer='SGD')
        d.load_weights('discrimiator')
        noise = np.random.uniform(-1, 1, (BATCH_SIZE * 20, 100))
        generated_images = g.predict(noise, verbose=1)
        d_pret = d.predict(generated_images, verbose=1)
        index = np.arange(0, BATCH_SIZE * 20)
        index.resize((BATCH_SIZE * 20, 1))
        pre_with_index = list(np.append(d_pret, index, axis=1))
        pre_with_index.sort(key=lambda x : x[0], reverse=True)
        nice_images = np.zeros((BATCH_SIZE, ) + generated_images.shape[1:3], dtype=np.float32)
        nice_image = nice_images[:, :, :, None]
        
        for i in range(BATCH_SIZE):
            idx = int(pre_with_index[i][1])
            nice_images[i, :, :, 0] = generated_images[idx, :, :, 0]
        image = combine_images(nice_images)
    else:
        noise = np.random.uniform(-1, 1, (BATCH_SIZE, 100))
        generated_images = g.predict(noise, verbose=0)
        image = combine_images(generated_images)
    image = image * 127.5 + 127.5
    Image.fromarray(image.astype(np.uint8)).save("C:/Users/Administrator/GAN/generated_image.png")
generate(32)

由於只迭代了10個epoch,效果不是很好,不過已經能看出手寫數字了。最后生成的圖片如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM