1. 程式人生 > >GAN-手寫數字識別-Keras

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,效果不是很好,不過已經能看出手寫數字了。最後生成的圖片如下: