天天看點

如何用 TensorFlow 實作生成式對抗網絡(GAN)

我們來研究一下生成式對抗網絡 GAN,并且用 TensorFlow 代碼實作。

自從 Ian Goodfellow 在 14 年發表了 論文 Generative Adversarial Nets 以來,生成式對抗網絡 GAN 廣受關注,加上學界大牛 Yann Lecun 在 Quora 答題時曾說,他最激動的深度學習進展是生成式對抗網絡,使得 GAN 成為近年來在機器學習領域的新寵,可以說,研究機器學習的人,不懂 GAN,簡直都不好意思出門。

下面我們來簡單介紹一下生成式對抗網絡,主要介紹三篇論文:1)Generative Adversarial Networks;2)Conditional Generative Adversarial Nets;3)Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks。

首先來看下第一篇論文,了解一下 GAN 的過程和原理:

GAN 啟發自博弈論中的二人零和博弈(two-player game),GAN 模型中的兩位博弈方分别由生成式模型(generative model)和判别式模型(discriminative model)充當。生成模型 G 捕捉樣本資料的分布,用服從某一分布(均勻分布,高斯分布等)的噪聲 z 生成一個類似真實訓練資料的樣本,追求效果是越像真實樣本越好;判别模型 D 是一個二分類器,估計一個樣本來自于訓練資料(而非生成資料)的機率,如果樣本來自于真實的訓練資料,D 輸出大機率,否則,D 輸出小機率。可以做如下類比:生成網絡 G 好比假币制造團夥,專門制造假币,判别網絡 D 好比警察,專門檢測使用的貨币是真币還是假币,G 的目标是想方設法生成和真币一樣的貨币,使得 D 判别不出來,D 的目标是想方設法檢測出

來 G 生成的假币。如圖所示:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

在訓練的過程中固定一方,更新另一方的網絡權重,交替疊代,在這個過程中,雙方都極力優化自己的網絡,進而形成競争對抗,直到雙方達到一個動态的平衡(納什均衡),此時生成模型 G 恢複了訓練資料的分布(造出了和真實資料一模一樣的樣本),判别模型再也判别不出來結果,準确率為 50%,約等于亂猜。

上述過程可以表述為如下公式:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

當固定生成網絡 G 的時候,對于判别網絡 D 的優化,可以這樣了解:輸入來自于真實資料,D 優化網絡結構使自己輸出 1,輸入來自于生成資料,D 優化網絡結構使自己輸出 0;當固定判别網絡 D 的時候,G 優化自己的網絡使自己輸出盡可能和真實資料一樣的樣本,并且使得生成的樣本經過 D 的判别之後,D 輸出高機率。

第一篇文章,在 MNIST 手寫資料集上生成的結果如下圖:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

最右邊的一列是真實樣本的圖像,前面五列是生成網絡生成的樣本圖像,可以看到生成的樣本還是很像真實樣本的,隻是和真實樣本屬于不同的類,類别是随機的。

第二篇文章想法很簡單,就是給 GAN 加上條件,讓生成的樣本符合我們的預期,這個條件可以是類别标簽(例如 MNIST 手寫資料集的類别标簽),也可以是其他的多模态資訊(例如對圖像的描述語言)等。用公式表示就是:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

式子中的 y 是所加的條件,結構圖如下:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

生成結果如下圖:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

圖中所加的條件 y 是類别标簽。

第三篇文章,簡稱(DCGAN),在實際中是代碼使用率最高的一篇文章,本系列文的代碼也是這篇文章代碼的初級版本,它優化了網絡結構,加入了 conv,batch_norm 等層,使得網絡更容易訓練,網絡結構如下:

如何用 TensorFlow 實作生成式對抗網絡(GAN)

可以有加條件和不加條件兩種網絡,論文還做了好多試驗,展示了這個網絡在各種資料集上的結果。有興趣同學可以去看論文,此文我們隻從代碼的角度了解去了解它。

參考文獻:

1. http://blog.csdn.net/solomon1558/article/details/52549409

前面我們了解了 GAN 的原理,下面我們就來用 TensorFlow 搭建 GAN(嚴格說來是 DCGAN,如無特别說明,本系列文章所說的 GAN 均指 DCGAN),如前面所說,GAN 分為有限制條件的 GAN,和不加限制條件的GAN,我們先來搭建一個簡單的 MNIST 資料集上加限制條件的 GAN。

首先下載下傳資料:在  /home/your_name/TensorFlow/DCGAN/ 下建立檔案夾 data/mnist,從 http://yann.lecun.com/exdb/mnist/ 網站上下載下傳 mnist 資料集 train-images-idx3-ubyte.gz,train-labels-idx1-ubyte.gz,t10k-images-idx3-ubyte.gz,t10k-labels-idx1-ubyte.gz 到 mnist 檔案夾下得到四個 .gz 檔案。

資料下載下傳好之後,在 /home/your_name/TensorFlow/DCGAN/ 下建立檔案 read_data.py 讀取資料,輸入如下代碼:

如何用 TensorFlow 實作生成式對抗網絡(GAN)
import osimport numpy as npdef read_data():    
# 資料目錄
    data_dir = '/home/your_name/TensorFlow/DCGAN/data/mnist'   
   # 打開訓練資料    
    fd = open(os.path.join(data_dir,'train-images-idx3-ubyte'))    
    # 轉化成 numpy 數組
    loaded = np.fromfile(file=fd,dtype=np.uint8)    
    # 根據 mnist 官網描述的資料格式,圖像像素從 16 位元組開始
    trX = loaded[16:].reshape((60000,28,28,1)).astype(np.float)   
   
    # 訓練 label
    fd = open(os.path.join(data_dir,'train-labels-idx1-ubyte'))
    loaded = np.fromfile(file=fd,dtype=np.uint8)
    trY = loaded[8:].reshape((60000)).astype(np.float)    
 
    # 測試資料
    fd = open(os.path.join(data_dir,'t10k-images-idx3-ubyte'))
    loaded = np.fromfile(file=fd,dtype=np.uint8)
    teX = loaded[16:].reshape((10000,28,28,1)).astype(np.float)    

    # 測試 label
    fd = open(os.path.join(data_dir,'t10k-labels-idx1-ubyte'))
    loaded = np.fromfile(file=fd,dtype=np.uint8)
    teY = loaded[8:].reshape((10000)).astype(np.float)

    trY = np.asarray(trY)
    teY = np.asarray(teY)    
    # 由于生成網絡由服從某一分布的噪聲生成圖檔,不需要測試集,
    # 是以把訓練和測試兩部分資料合并
    X = np.concatenate((trX, teX), axis=0)
    y = np.concatenate((trY, teY), axis=0)    
    # 打亂排序
    seed = 547
    np.random.seed(seed)
    np.random.shuffle(X)
    np.random.seed(seed)
    np.random.shuffle(y)    
    # 這裡,y_vec 表示對網絡所加的限制條件,這個條件是類别标簽,
    # 可以看到,y_vec 實際就是對 y 的獨熱編碼,關于什麼是獨熱編碼,
    # 請參考 http://www.cnblogs.com/Charles-Wan/p/6207039.html
    y_vec = np.zeros((len(y), 10), dtype=np.float)    
    for i, label in enumerate(y):
         y_vec[i,y[i]] = 1.0    
    return X/255., y_vec      
如何用 TensorFlow 實作生成式對抗網絡(GAN)

這裡順便說明一下,由于 MNIST 資料總體占得記憶體不大(可以看下載下傳的檔案,最大的一個 45M 左右,)是以這樣讀取資料是允許的,一般情況下,資料特别龐大的時候,建議把資料轉化成 tfrecords,用 TensorFlow 标準的資料讀取格式,這樣能帶來比較高的效率。

然後,定義一些基本的操作層,例如卷積,池化,全連接配接等層,在 /home/your_name/TensorFlow/DCGAN/ 建立檔案 ops.py,輸入如下代碼:

如何用 TensorFlow 實作生成式對抗網絡(GAN)
import tensorflow as tf
from tensorflow.contrib.layers.python.layers import batch_norm as batch_norm

# 常數偏置
def bias(name, shape, bias_start = 0.0, trainable = True):
    
    dtype = tf.float32
    var = tf.get_variable(name, shape, tf.float32, trainable = trainable, 
                          initializer = tf.constant_initializer(
                                                  bias_start, dtype = dtype))    
return var
 
# 随機權重
def weight(name, shape, stddev = 0.02, trainable = True):
    
    dtype = tf.float32
    var = tf.get_variable(name, shape, tf.float32, trainable = trainable, 
                          initializer = tf.random_normal_initializer(
                                              stddev = stddev, dtype = dtype))    
 
return var
 
# 全連接配接層
def fully_connected(value, output_shape, name = 'fully_connected', with_w = False):
    
    shape = value.get_shape().as_list()
    
    with tf.variable_scope(name):
        weights = weight('weights', [shape[1], output_shape], 0.02)
        biases = bias('biases', [output_shape], 0.0)        
    if with_w:        
return tf.matmul(value, weights) + biases, weights, biases
     else:
        return tf.matmul(value, weights) + biases
 
# Leaky-ReLu 層
def lrelu(x, leak=0.2, name = 'lrelu'):
    
    with tf.variable_scope(name):
        return tf.maximum(x, leak*x, name = name)        # ReLu 層def relu(value, name = 'relu'):
    with tf.variable_scope(name):
        return tf.nn.relu(value)    # 解卷積層
def deconv2d(value, output_shape, k_h = 5, k_w = 5, strides =[1, 2, 2, 1], 
             name = 'deconv2d', with_w = False):
    
    with tf.variable_scope(name):
        weights = weight('weights', 
                         [k_h, k_w, output_shape[-1], value.get_shape()[-1]])
        deconv = tf.nn.conv2d_transpose(value, weights, 
                                        output_shape, strides = strides)
        biases = bias('biases', [output_shape[-1]])
        deconv = tf.reshape(tf.nn.bias_add(deconv, biases), deconv.get_shape())
         if with_w:
             return deconv, weights, biases
         else:
            return deconv            # 卷積層
            def conv2d(value, output_dim, k_h = 5, k_w = 5, 
            strides =[1, 2, 2, 1], name = 'conv2d'):
    
    with tf.variable_scope(name):
        weights = weight('weights', 
                         [k_h, k_w, value.get_shape()[-1], output_dim])
        conv = tf.nn.conv2d(value, weights, strides = strides, padding = 'SAME')
        biases = bias('biases', [output_dim])
        conv = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape())        
        return conv
 
# 把限制條件串聯到 feature map
def conv_cond_concat(value, cond, name = 'concat'):    
    # 把張量的次元形狀轉化成 Python 的 list
    value_shapes = value.get_shape().as_list()
    cond_shapes = cond.get_shape().as_list()    
    # 在第三個次元上(feature map 次元上)把條件和輸入串聯起來,
    # 條件會被預先設為四維張量的形式,假設輸入為 [64, 32, 32, 32] 維的張量,
    # 條件為 [64, 32, 32, 10] 維的張量,那麼輸出就是一個 [64, 32, 32, 42] 維張量
    with tf.variable_scope(name):        
        return tf.concat(3, [value, 
                             cond * tf.ones(value_shapes[0:3] + cond_shapes[3:])])  # BN 層,這裡我們直接用官方的 BN 層。
        def batch_norm_layer(value, is_train = True, name = 'batch_norm'):
    
    with tf.variable_scope(name) as scope:
        if is_train:        
            return batch_norm(value, decay = 0.9, epsilon = 1e-5, scale = True, 
                              is_training = is_train, 
                              updates_collections = None, scope = scope)
         else:
            return batch_norm(value, decay = 0.9, epsilon = 1e-5, scale = True, 
                              is_training = is_train, reuse = True, 
                              updates_collections = None, scope = scope)      
如何用 TensorFlow 實作生成式對抗網絡(GAN)

batch_norm 裡的 decay 指的是滑動平均的 decay,epsilon 作用是加到分母 variance 上避免分母為零,scale 是個布爾變量,如果為真值 True, 結果要乘以 gamma,否則 gamma 不使用,is_train 也是布爾變量,為真值代表訓練過程,否則代表測試過程(在 BN 層中,訓練過程和測試過程是不同的,具體請參考論文:https://arxiv.org/abs/1502.03167)。關于 batch_norm 的其他的參數,請看參考文獻2。

參考文獻:

1. https://github.com/carpedm20/DCGAN-tensorflow

2. https://github.com/tensorflow/tensorflow/blob/b826b79718e3e93148c3545e7aa3f90891744cc0/tensorflow/contrib/layers/python/layers/layers.py#L100 

繼續閱讀