1. 程式人生 > >生成對抗網路的簡單介紹(TensorFlow 程式碼)

生成對抗網路的簡單介紹(TensorFlow 程式碼)

原文地址:

引言

最近,研究者們對生成模型的興趣一直很大(參見OpenAI的這篇部落格文章)。這些生成模型是可以學習建立類似於我們給它們的資料。這樣的直觀感受是,如果我們可以得到一個能寫出高質量的新聞文章的模型,那麼它一般也會學到很多關於新聞文章的內容。換句話說,這個模型也應該有一個關於新聞文章的很好的內部表示。然後,我們可以希望使用這種表示來幫助我們進行其他相關任務,例如按主題分類新聞文章。

實際上,像這樣製作資料的訓練模式並不容易,但是近年來,出現了一些能夠執行得很好的方法。其中之一便是生成對抗網路(GAN)。著名的深度學習研究人員和Facebook AI研究主管Yann LeCun最近引用GAN作為

深度學習中最重要的新發展之一

“There are many interesting recent development in deep learning…The most important one, in my opinion, is adversarial training (also called GAN for Generative Adversarial Networks). This, and the variations that are now being proposed is the most interesting idea in the last 10 years in ML, in my opinion.” – Yann LeCun

判別模型與生成模型

在看GAN之前,讓我們簡單回顧一下生成模式和判別模型之間的區別:

  • 判別模型學習將輸入資料(x)對映到某個所需輸出類標籤(y)的函式。從概率出發,它們直接學習條件分佈Py|x
  • 生成模型嘗試同時學習輸入資料和標籤的聯合概率,即Pxy。這可以通過貝葉斯規則轉換為Py|x進行分類,但生成能力也可以用於別的東西,例如建立可能的新xy 樣本。

兩種型別的模型都是有用的,但是生成模型比判別模型有一個有趣的優勢 - 即使沒有標籤,它們也有潛力理解和解釋輸入資料的基礎結構。在現實世界中處理資料建模問題時,這是非常可取的,因為未標記的資料當然是豐富的,但是獲得標籤資料通常是非常昂貴,不切實際的。

生成對抗網路

GANs是一個有趣的想法,由德國蒙特利爾大學的Ian Goodfellow(現OpenAI)領導的一組研究人員於2014年首次推出。 GAN的主要思想是擁有兩個競爭的神經網路模型。 一個將噪聲資料作為輸入,併產生樣本(所謂的生成器)。 另一個模型(稱為判別器)從生成器和訓練資料接收樣本,並且必須能夠區分兩個來源。 這兩個網路進行連續的博弈,生成器學習產生越來越多的現實樣本,鑑別器正在學習越來越好地區分生成的資料和實際資料。 這兩個網路同時進行訓練,最後的希望是競爭能夠使生成器生成的樣本與實際資料不可區分。

這裡寫圖片描述

這裡經常使用的類比是,生成器就像偽造的一些物品,而判別器就像警方試圖檢測偽造的物品。這種設定也可能似乎讓人聯想到強化學習,其中生成器從判別器接收到獎勵訊號,讓它知道生成的資料是否準確。然而,與GAN的關鍵區別在於,我們可以將梯度資訊從判別器反向傳播回生成器網路,因此生成器知道如何調整其引數,以產生可以欺騙判別器的輸出資料。

到目前為止,GAN主要應用於建模自然影象。他們現在在影象生成任務中產生出色的結果,產生的影象比基於最大可能性訓練目標的其他領先的生成方法訓練有素的影象更加尖銳。以下是GAN生成的影象示例:

Generated bedrooms. Source: “Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks”

Generated bedrooms. Source: “Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks” https://arxiv.org/abs/1511.06434v2*

這裡寫圖片描述

Generated CIFAR-10 samples. Source: “Improved Techniques for Training GANs” https://arxiv.org/abs/1606.03498*

近似一維高斯分佈

為了更好地瞭解這一切如何工作,我們將在TensorFlow中使用GAN來解決一個簡單的問題 - 學習近似一維高斯分佈。 這是基於Eric Jang的類似目標的博文。 我們的演示的完整原始碼可以在Github(https://github.com/AYLIEN/gan-intro)上找到,在這裡我們將專注於一些更有趣的部分程式碼。

首先我們建立“真實”資料分佈,一個簡單的高斯,平均值為4,標準偏差為0.5。 它具有一個樣本函式,它從分佈返回給定數量的樣本(按值排序)。

    class DataDistribution(object):
        def init(self):
            self.mu = 4
            self.sigma = 0.5
        def sample(self, N):
            samples = np.random.normal(self.mu, self.sigma, N)
            samples.sort()
            return samples 

我們將嘗試學習的資料分佈如下所示:

這裡寫圖片描述

我們還定義生成器輸入噪聲分佈(具有相似的取樣功能)。 在Eric Jang的例子之後,我們還採用分層取樣方法對生成器輸入噪聲進行分析 - 樣本首先在指定範圍內均勻生成,然後隨機擾動。

  class GeneratorDistribution(object):
        def __init__(self, range):
            self.range = range

        def sample(self, N):
            return np.linspace(-self.range, self.range, N) + \
                np.random.random(N) * 0.01

我們的生成器和判別器網路非常簡單。 生成器是通過非線性(softplus函式)的線性變換,接著是另一個線性變換。

    def generator(input, hidden_size):
        h0 = tf.nn.softplus(linear(input, hidden_size, 'g0'))
        h1 = linear(h0, 1, 'g1')
        return h1

在這種情況下,我們發現重要的是確保判別器比發生器更強大,否則它們沒有足夠的能力學習從而準確區分生成的和實際的樣本。 所以我們做了一個更深層的神經網路,具有更大的維度。 它使用除了最後一個層之外的所有層中的tanh非線性,其是sigmoid(其輸出可以被解釋為概率)。

   def discriminator(input, hidden_size):
        h0 = tf.tanh(linear(input, hidden_size * 2, 'd0'))
        h1 = tf.tanh(linear(h0, hidden_size * 2, 'd1'))
        h2 = tf.tanh(linear(h1, hidden_size * 2, 'd2'))
        h3 = tf.sigmoid(linear(h2, 1, 'd3'))
        return h3

然後,我們可以在TensorFlow圖中連線這些部件。 我們還為每個網路定義損失函式,生成器的目的是簡單地愚弄判別器。

  with tf.variable_scope('G'):
        z = tf.placeholder(tf.float32, shape=(None, 1))
        G = generator(z, hidden_size)

    with tf.variable_scope('D') as scope:
        x = tf.placeholder(tf.float32, shape=(None, 1))
        D1 = discriminator(x, hidden_size)
        scope.reuse_variables()
        D2 = discriminator(G, hidden_size)

    loss_d = tf.reduce_mean(-tf.log(D1) - tf.log(1 - D2))
    loss_g = tf.reduce_mean(-tf.log(D2))

我們使用TensorFlow中的普通GradientDescentOptimizer以指數學習速率衰減為每個網路建立優化器。 我們還應該注意,在這裡找到好的優化引數需要一些調整。

    def optimizer(loss, var_list):
        initial_learning_rate = 0.005
        decay = 0.95
        num_decay_steps = 150
        batch = tf.Variable(0)
        learning_rate = tf.train.exponential_decay(
            initial_learning_rate,
            batch,
            num_decay_steps,
            decay,
            staircase=True
        )
        optimizer = GradientDescentOptimizer(learning_rate).minimize(
            loss,
            global_step=batch,
            var_list=var_list
        )
        return optimizer

    vars = tf.trainable_variables()
    d_params = [v for v in vars if v.name.startswith('D/')]
    g_params = [v for v in vars if v.name.startswith('G/')]

    opt_d = optimizer(loss_d, d_params)
    opt_g = optimizer(loss_g, g_params)

為了訓練模型,我們從資料分佈和噪聲分佈中抽取樣本,並優化判別器和生成器的引數。

  with tf.Session() as session:
        tf.initialize_all_variables().run()

        for step in xrange(num_steps):
            # update discriminator
            x = data.sample(batch_size)
            z = gen.sample(batch_size)
            session.run([loss_d, opt_d], {
                x: np.reshape(x, (batch_size, 1)),
                z: np.reshape(z, (batch_size, 1))
            })

            # update generator
            z = gen.sample(batch_size)
            session.run([loss_g, opt_g], {
                z: np.reshape(z, (batch_size, 1))
            })

以下動畫顯示了生成器如何在訓練過程中學習如何近似資料分佈:

我們可以看到,在訓練過程開始時,生成器正在產生與實際資料非常不同的分佈。 它最終終於學會了相當接近與真實資料(在750幀附近),然後收斂到一個較窄的分佈集中在輸入分佈的平均值。 訓練後,這兩個分佈看起來像這樣:

這裡寫圖片描述

這是很直觀的。 生成器正在從實際資料和我們的判別器中檢視各個樣本。 如果生成器只是在這個簡單的例子中產生實際資料的平均值,那麼很可能會愚弄判別器。

這個問題有很多可能的解決方案。 在這種情況下,我們可以新增一些提前停止的標準,當達到兩個分佈之間的相似性閾值時暫停訓練。 然而,如果將這個概念化為更大的問題,即使在簡單的情況下也可能難以保證,我們的生成器將始終達到提前停止的意義。 一個更有吸引力的解決方案是通過給判別者一次性檢查多個示例的能力來直接解決問題。

提高樣本多樣性

根據Tim Salimans和OpenAI的合作者最近的一篇文章,生成器崩潰到其輸出非常窄的點分佈的引數設定的問題是GAN的主要失敗模式之一。 幸運的是,他們還提出了一個解決方案:允許判別器同時檢視多個樣本,這是一種稱之為“小批量判別”的技術。

在這篇文章中,小批量判別被定義為任何判別器能夠檢視整批樣本以便確定它們是來自生成器還是實際資料的方法。 他們還提出了一種更具體的演算法,通過建模給定樣品與同一批次中的所有其他樣品之間的距離來工作。 然後將這些距離與原始樣品組合並通過鑑別器,因此可以選擇在分類過程中使用距離測量值和樣品值。

該方法可以大致地總結如下:

  • 取出判別器的一些中間層的輸出。
  • 將其乘以3D張量以產生矩陣(在下面的程式碼中大小為num_kernels x kernel_dim)。
  • 在批量中的所有樣本之間計算該矩陣中的行之間的L1距離,然後應用負指數。
  • 樣本的minibatch 特徵是這些取冪距離的總和。
  • 將原始輸入連線到新建立的最小匹配特徵的最小匹配層(前一個判別層的輸出),並將其作為輸入傳遞給判別的下一層。

在TensorFlow中:

  def minibatch(input, num_kernels=5, kernel_dim=3):
        x = linear(input, num_kernels * kernel_dim)
        activation = tf.reshape(x, (-1, num_kernels, kernel_dim))
        diffs = tf.expand_dims(activation, 3) - \
            tf.expand_dims(tf.transpose(activation, [1, 2, 0]), 0)
        abs_diffs = tf.reduce_sum(tf.abs(diffs), 2)
        minibatch_features = tf.reduce_sum(tf.exp(-abs_diffs), 2)
        return tf.concat(1, [input, minibatch_features])

我們實施了這種 minibatch discrimination技術,看看它是否有助於我們的示例中生成器輸出分佈的崩潰。 訓練期間生成器網路的新行為如下所示。

在這種情況下,很明顯,新增 minibatch discrimination會導致生成器維持原始資料分佈的大部分寬度(儘管仍然不完美)。 收斂後,分佈現在看起來像這樣:

這裡寫圖片描述

minibatch discrimination的最後一點是,使批量大小作為超引數更為重要。 在我們的示例中,我們不得不保持批量相當小(不到16個左右)進行訓練。 也許僅僅限制對每個距離測量有貢獻的樣本數量,而不是使用完整批次,但是再次調整另一個引數就足夠了。

最後的想法

生成對抗網路是一個有趣的發展,為我們提供了一種新的無監督學習方法。 GAN的大部分成功應用一直處於計算機視覺領域,但在這裡,我們正在研究將這些技術應用於自然語言處理的方法。如果您正在處理相同的想法,並希望比較想法,請聯絡我們。

在這方面的一個大問題是如何最好地評估這些模型。在影象域中,至少看看生成的樣本是很容易的,雖然這顯然不是令人滿意的解決方案。在文字領域,這甚至不太有用(除非你的目標是產生散文)。使用基於最大似然訓練的生成模型,我們通常可以根據看不見的測試資料的可能性(或可能性的一些下限)產生一些度量,但這在這裡是不適用的。一些GAN論文根據生成的樣本的核密度估計值產生了似然估計,但是這種技術似乎在較高維空間中分解。另一個解決方案是隻評估一些下游任務(如分類)。如果您有任何其他建議,我們很樂意聽到您的意見。

更多資訊

如果您想了解更多有關GAN的資訊,我們建議您從以下出版物開始:

  • Generative Adversarial Networks
  • Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks
  • InfoGAN: Interpretable Representation Learning by Information Maximizing Generative Adversarial Nets
  • Improved Techniques for Training GANs

隨意重用我們的GAN程式碼,當然還要關注我們的部落格。 歡迎評論,更正和反饋。

原文地址: