# 六、自編碼器,變分自編碼器和生成對抗網絡
本章將介紹一種與到目前為止所看到的模型稍有不同的模型。 到目前為止提供的所有模型都屬于一種稱為判別模型的模型。 判別模型旨在找到不同類別之間的界限。 他們對找到`P(Y|X)`-給定某些輸入`X`的輸出`Y`的概率感興趣。 這是用于分類的自然概率分布,因為您通常要在給定一些輸入`X`的情況下找到標簽`Y`。
但是,還有另一種類型的模型稱為生成模型。 建立了生成模型以對不同類的分布進行建模。 他們對找到`P(Y,X)`-輸出`Y`和輸入`X`一起出現的概率分布感興趣。 從理論上講,如果您可以捕獲數據中類別的概率分布,則將了解更多信息,并且可以使用貝葉斯規則來計算`P(Y|X)`。
生成模型屬于無監督學習算法的類別。 無監督意味著我們不需要標簽數據。
本章列出了一些我們將要學習的關鍵主題:
* 自編碼器
* 變分自編碼器
* 生成對抗網絡
* 實現各種生成模型來生成手寫數字
# 為什么是生成模型
在下圖中,我們可以看到生成模型和判別模型之間的主要區別。 使用判別模型,我們通常嘗試找到在數據中不同類別之間進行區分或“區分”的方法。 但是,使用生成模型,我們嘗試找出數據的概率分布。 在圖示中,分布由包含較小圓圈的藍色和黃色大斑點表示。 如果我們從數據中學到這種分布,我們將能夠采樣或“生成”應該屬于它的新數據點,例如紅色三角形。

嘗試捕獲數據集的概率分布具有以下用例:
* 使用未標記的數據預訓練模型
* 擴展數據集(理論上,如果您捕獲數據的概率分布,則可以生成更多數據)
* 壓縮數據(有損)
* 創建某種模擬器(例如,可以通過四個輸入來控制四軸飛行器;如果捕獲此數據并在其上訓練生成模型,則可以學習無人機的動態)
使用生成模型時的期望是,如果我們能夠創建類似于原始輸入數據的新數據,則我們的模型必須了解一些有關數據分布的知識。
訓練了生成神經網絡模型,以產生類似于訓練集的數據樣本。 由于模型參數的數量小于訓練數據的維數,因此迫使模型發現有效的數據表示形式。
# 自編碼器
我們將要看到的第一個生成模型是自編碼器模型。 自編碼器是一個簡單的神經網絡,由兩部分組成:**編碼器**和**解碼器**。 這個想法是編碼器部分會將您的輸入壓縮到較小的尺寸。 然后,從這個較小的維度嘗試使用模型的解碼器部分重建輸入。 通常用許多名稱來稱呼這種較小的尺寸,例如潛在空間,隱藏空間,嵌入或編碼。
如果自編碼器能夠再現其輸入,則從理論上講,該潛在空間應該對表示原始數據所需的所有重要信息進行編碼,但其優點是尺寸小于輸入。 編碼器可以被認為是一種壓縮輸入數據的方式,而解碼器是一種將其解壓縮的方式。 在下圖中,我們可以看到一個簡單的自編碼器的外觀。 我們的潛在空間或編碼是中間標記為`z`的部分。

傳統上,在自編碼器中,構成網絡的層只是全連接層,但是通過使用卷積層,自編碼器也可以擴展到圖像。 與之前一樣,編碼器會將輸入圖像壓縮為較小的表示形式,而解碼器將盡最大努力恢復信息。 區別在于,編碼器現在是將數據壓縮為特征向量的 CNN,而不是具有全連接層的 ANN,并且解碼器將使用轉置的卷積層從編碼中重新生成圖像。
此處提供了一個自編碼器處理圖像的示例。 對于解碼器部分

對于任何自編碼器,損失函數都會引導編碼器和解碼器重建輸入。 使用的常見損失是自編碼器的輸出與網絡輸入之間的 L2 損失。 我們現在應該問自己一個問題:“使用 L2 損失比較圖像是一個好主意嗎?”。 如果您拍攝以下圖像,即使它們看起來截然不同,它們實際上彼此之間的距離`L2`也相同:

這表明當您使用 L2 損失比較圖像時,并非總是可以依靠它,因此在使用它時應牢記這一點。
# 卷積自編碼器示例
以下 TensorFlow 代碼將為 MNIST 數據集構建卷積自編碼器模型。 代碼的第一部分將構建模型,編碼器和解碼器的圖。 在代碼中,我們突出顯示模型的一部分,其輸出將是我們的潛在向量:
```py
class CAE_CNN(object):
def __init__(self, img_size = 28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14, W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2),
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7, W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2),
filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.reshape(self.__conv2_act, [tf.shape(self.__x)[0], 7 * 7 * 32])
self.__guessed_z = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="latent_var")
tf.summary.histogram("latent", self.__guessed_z)
with tf.name_scope('DECODER'):
##### DECODER (At this point we have 1x18x64
self.__z_develop = tf.layers.dense(inputs=self.__guessed_z,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
# We want the output flat for using on the loss
self.__y_flat = tf.reshape(self.__y, [tf.shape(self.__x)[0], 28 * 28])
```
與卷積自編碼器損失有關的代碼段如下:
```py
with tf.name_scope("CAE_LOSS"):
# L2 loss
loss = tf.losses.mean_squared_error(labels=model_in, predictions=model_out_flat)
# Solver configuration
with tf.name_scope("Solver"):
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)
```
# 自編碼器的用途和局限性
自編碼器的簡單性很酷,但是在功能上有所限制。 他們的一個潛在用途是預訓練模型(假設您將模型作為編碼器部分,并且能夠創建反模型作為解碼器)。 使用自編碼器可以很好地進行預訓練,因為您可以獲取數據集并訓練自編碼器以對其進行重構。 訓練后,您可以使用編碼器的權重,然后將其微調到您要執行的任務。
如果不太復雜,則另一種用途是作為數據壓縮形式。 您可以使用自編碼器將維數減小到兩維或三維,然后嘗試在潛在空間中可視化您的輸入以查看它是否對您有用。
但是,自編碼器的局限性在于它們不能用于為我們生成更多數據。 這是因為我們不知道如何創建新的潛在向量來饋送到解碼器。 唯一的方法是在輸入數據上使用編碼器。 現在,我們將研究對自編碼器的修改,以幫助解決此問題。
# 變分自編碼器
我們第一個可以生成更多類似于訓練數據的真實生成模型,將是**變分自編碼器**(**VAE**)。 VAE 看起來像正常的自編碼器,但有一個新的約束,它將迫使我們的壓縮表示(潛伏空間)遵循零均值和單位方差高斯分布。
在潛在空間上施加此約束的想法是,當我們想使用 VAE 生成新數據時,我們可以創建來自單位高斯分布的樣本向量,并將其提供給經過訓練的解碼器。 VAE 和常規自編碼器之間的差異就是對潛在空間向量的約束。 這個約束條件使我們可以創建一種新的潛在向量,然后再將其饋送到解碼器以生成數據。
下圖顯示,VAE 在結構上與自編碼器完全相同,除了對隱藏空間的約束之外:

# 定義正態分布的參數
我們需要兩個參數來跟蹤并強制我們的 VAE 模型在潛在空間中產生正態分布:
* **平均值**(應為零)
* **標準差**(應為 1)
在下圖中,我們給出了具有不同均值和標準差值的正態分布示例。 僅使用這兩個值,我們就可以產生一個正態分布,可以從中采樣:

# VAE 損失函數
在 VAE 中,損失函數由兩部分組成:
* **生成損失**:此損失將模型輸出與模型輸入進行比較。 這可能是我們在自編碼器中使用的損失,例如 L2 損失。
* **潛在損失**:此損失將潛在向量與零均值,單位方差高斯分布進行比較。 我們在這里使用的損失將是 KL 散度損失。 如果 VAE 開始產生不是來自所需分布的潛在向量,則該損失項將對 VAE 造成不利影響。
以下屏幕截圖顯示了 VAE 的損失,它是生成損失和潛在空間損失的組合:

# Kullback-Leibler 散度
KL 散度損失將產生一個數字,該數字指示兩個分布彼此之間的接近程度。
兩個分布之間的距離越近,損失就越低。 在下圖中,藍色分布正在嘗試對綠色分布進行建模。 隨著藍色分布越來越接近綠色分布,KL 散度損失將接近于零。

# 訓練 VAE
為了訓練 VAE 并使用 KL 散度損失,我們首先需要研究如何生成潛向量。 我們將使編碼器產生兩個向量,而不是讓編碼器直接精確地產生一個潛在向量。 第一個是平均值的向量`μ`,第二個是標準差值的向量`σ`。 根據這些,我們可以創建第三個向量,其中使用`μ`和`σ`從高斯分布中采樣元素向量的第`i`個值作為該高斯分布的均值和標準差。 然后,該第三采樣向量被發送到解碼器。
現在,我們的模型如下所示:

上圖中的均值和標準差塊將只是正常的全連接層,它們將通過 KL 損失函數來學習如何返回所需的值。 更改我們如何獲得潛向量的原因是因為它使我們能夠輕松計算 KL 散度損失。 KL 損失現在如下:`latent_mean`為`μ`,`latent_stddev`為`σ`:
```py
0.5 * tf.reduce_sum(tf.square(latent_mean) + tf.square(latent_stddev) - tf.log(tf.square(latent_stddev)) - 1, 1)
```
不幸的是,有一個**樣本**塊,您可以將其視為隨機生成器節點,無法微分。 這意味著我們不能使用反向傳播來訓練我們的 VAE。 我們需要一種稱為“重新參數化”技巧的東西,該技巧將從反向傳播流中取出采樣。
# 重新參數化技巧
重新參數化技巧的想法是從反向傳播循環中取出隨機樣本節點。 它是通過從高斯分布中獲取樣本ε,然后將其乘以我們的標準差向量`σ`的結果,然后加上`μ`來實現的。 現在,我們的潛在向量的公式是:


產生的潛向量將與以前相同,但是現在進行此更改可以使梯度流回到 VAE 的編碼器部分。 下圖顯示了在進行重新參數化之前的 VAE 模型,在左側進行了重新參數化之后。 藍色框是損失函數的兩個部分。 查看該圖,您可以看到我們的梯度現在可以向后流動,因為我們不再具有紅色框(示例節點)來擋路了:

這是 TensorFlow 中的重新參數化形式:
```py
# Add linear ops to produce mean and standard devation vectors
fc_mean = tf.layers.dense(self.__enc_out, units=latent_size, activation=None, name="w_mean")
fc_stddev = tf.layers.dense(self.__enc_out, units=latent_size, activation=None, name="w_stddev")
# Generate normal distribution with dimensions [Batch, latent_size]
sample_block = tf.random_normal([tf.shape(X)[0], latent_size], 0, 1, dtype=tf.float32)
latent_z = fc_mean + (fc_stddev * sample_block)
```
# 卷積變分自編碼器代碼
現在我們可以將所有內容組合在一起,并提供 TensorFlow 代碼,這些代碼將為 MNIST 數據集構建卷積 VAE。 我們為 VAE 模型創建一個類,然后將該模型放入`__init__`方法中。 第一部分是我們的模型的編碼器,由兩個轉換層組成:
```py
class VAE_CNN(object):
def __init__(self, img_size=28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, img_size * img_size], name='IMAGE_IN')
self.__x_image = tf.reshape(self.__x, [-1, img_size, img_size, 1])
with tf.name_scope('ENCODER'):
##### ENCODER
# CONV1: Input 28x28x1 after CONV 5x5 P:2 S:2 H_out: 1 + (28+4-5)/2 = 14, W_out= 1 + (28+4-5)/2 = 14
self.__conv1_act = tf.layers.conv2d(inputs=self.__x_image, strides=(2, 2),
filters=16, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
# CONV2: Input 14x14x16 after CONV 5x5 P:0 S:2 H_out: 1 + (14+4-5)/2 = 7, W_out= 1 + (14+4-5)/2 = 7
self.__conv2_act = tf.layers.conv2d(inputs=self.__conv1_act, strides=(2, 2),
filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu)
```
接下來是 VAE 的一部分,該部分負責使用我們之前的新重新參數化技巧來創建潛在向量。 我們添加了對最終潛在向量的記錄,以檢查它是否按照我們期望的那樣遵循單位高斯分布產生向量:
```py
with tf.name_scope('LATENT'):
# Reshape: Input 7x7x32 after [7x7x32]
self.__enc_out = tf.reshape(self.__conv2_act, [tf.shape(self.__x)[0], 7 * 7 * 32])
# Add linear ops for mean and variance
self.__w_mean = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="w_mean")
self.__w_stddev = tf.layers.dense(inputs=self.__enc_out,
units=latent_size, activation=None, name="w_stddev")
# Generate normal distribution with dimensions [B, latent_size]
self.__samples = tf.random_normal([tf.shape(self.__x)[0], latent_size], 0, 1, dtype=tf.float32)
self.__guessed_z = self.__w_mean + (self.__w_stddev * self.__samples)
tf.summary.histogram("latent_sample", self.__guessed_z)
```
之后,我們添加網絡的解碼器部分,該部分包括一個全連接層,然后是兩個轉置的卷積層:
```py
with tf.name_scope('DECODER'):
##### DECODER
# Linear layer
self.__z_develop = tf.layers.dense(inputs=self.__guessed_z,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
# Model output
self.__y_flat = tf.reshape(self.__y, [tf.shape(self.__x)[0], 28 * 28])
```
與我們的模型分開,我們需要寫出最終損失函數,該函數將用于訓練 VAE。 然后,我們可以將這種損失傳遞給我們選擇的優化器,以創建我們的訓練步驟:
```py
# Loss function
with tf.name_scope("VAE_LOSS"):
# L2 loss (generative loss)
generation_loss = tf.losses.mean_squared_error(labels=model_in, predictions= model_out_flat)
# KL Loss (latent loss)
latent_loss = 0.5 * tf.reduce_sum(tf.square(z_mean) + tf.square(z_stddev) - tf.log(tf.square(z_stddev)) - 1, 1)
# Merge the losses
```
```py
loss = tf.reduce_mean(generation_loss + latent_loss)
# Solver
with tf.name_scope("Solver"):
train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)
```
# 產生新數據
訓練完 VAE 模型后,我們可以將其解碼器部分截斷,并用作生成器為我們生成新數據。 它將通過向它提供來自單位高斯分布的新潛在向量來工作。
我們在 TensorFlow 中提供負責構建此生成的 VAE 圖的代碼,如下所示:
```py
class VAE_CNN_GEN(object):
def __init__(self, img_size=28, latent_size=20):
self.__x = tf.placeholder(tf.float32, shape=[None, latent_size], name='LATENT_IN')
with tf.name_scope('DECODER'):
# Linear layer
self.__z_develop = tf.layers.dense(inputs=self.__x,
units=7 * 7 * 32, activation=None, name="z_matrix")
self.__z_develop_act = tf.nn.relu(tf.reshape(self.__z_develop, [tf.shape(self.__x)[0], 7, 7, 32]))
# DECONV1
self.__conv_t2_out_act = tf.layers.conv2d_transpose(inputs=self.__z_develop_act,
strides=(2, 2), kernel_size=[5, 5], filters=16,
padding="same", activation=tf.nn.relu)
# DECONV2
# Model output
self.__y = tf.layers.conv2d_transpose(inputs=self.__conv_t2_out_act,
strides=(2, 2), kernel_size=[5, 5], filters=1,
padding="same", activation=tf.nn.sigmoid)
@property
def output(self):
return self.__y
@property
def input(self):
return self.__x
```
# 生成對抗網絡
**生成對抗網絡**(**GAN**)是另一種非常新的生成模型,由于其令人印象深刻的結果而受到關注。 GAN 由兩個網絡組成:生成器網絡和判別器網絡。 在訓練過程中,他們倆都玩零和游戲,其中判別器網絡試圖發現輸入到其中的圖像是真實的還是偽造的。 同時,生成器網絡嘗試創建足以欺騙判別器的偽造圖像。
想法是經過一段時間的訓練,判別器和生成器都非常擅長于他們的任務。 結果,生成器被迫嘗試創建看起來越來越接近原始數據集的圖像。 為此,它必須捕獲數據集的概率分布。
下圖概述了此 GAN 模型的外觀:

判別器和生成器都將具有自己的損失函數,但是它們的損失都相互依賴。
讓我們總結一下 GAN 模型的兩個主要模塊或網絡:
* **生成器**:使用大小為 N 的一維向量作為輸入,創建類似于*真實圖像*數據集的圖像(選擇 N 取決于我們)
* **判別器**:驗證提供給它的圖像是真實的還是偽造的
GAN 的一些實際用法如下:
* 使用判別器網絡權重作為不同任務的初始化,類似于我們對自編碼器可以執行的操作
* 使用生成器網絡創建新圖像,可能會擴大您的數據集,就像我們可以使用經過訓練的 VAE 解碼器一樣
* 將判別器用作損失函數(對于圖像,可能優于 L1/L2),并且也可以在 VAE 中使用
* 通過將生成的數據與標記的數據混合來進行半監督學習
現在我們將向您展示如何在 TensorFlow 中實現非常簡單的 GAN。 一旦經過訓練,我們的 GAN 的生成器部分就可以用于根據 100 個長隨機噪聲向量創建 MNIST 手寫數字。 讓我們開始吧!
# 判別器
我們需要做的第一件事就是創建我們的判別網絡。 為此,我們將幾個全連接層堆疊在一起。 判別器將 784 個長度向量作為輸入,這是我們的`28x28` MNIST 圖像變平。 每個圖像的輸出將只是一個數字,這是鑒別者對該圖像為真實圖像的信心程度的分數。 我們使用 Leaky ReLu 作為激活函數,以防止 ReLu 單元死亡。
我們返回原始對率,因為損失函數將為我們應用 Sigmoid 激活函數,以確保判別器輸出在 0 到 1 之間:
```py
def discriminator(x):
with tf.variable_scope("discriminator"):
fc1 = tf.layers.dense(inputs=x, units=256, activation=tf.nn.leaky_relu)
fc2 = tf.layers.dense(inputs=fc1, units=256, activation=tf.nn.leaky_relu)
logits = tf.layers.dense(inputs=fc2, units=1)
return logits
```
# 生成器
現在我們創建生成器網絡。 生成器的工作是將隨機噪聲的向量作為輸入,并從中生成輸出圖像。 在此示例中,我們再次使用全連接層,這些層最后將產生 784 個長向量的輸出,我們可以對其進行整形以獲得`28x28`的圖像:
```py
def generator(z):
with tf.variable_scope("generator"):
fc1 = tf.layers.dense(inputs=z, units=1024, activation=tf.nn.relu)
fc2 = tf.layers.dense(inputs=fc1, units=1024, activation=tf.nn.relu)
img = tf.layers.dense(inputs=fc2, units=784, activation=tf.nn.tanh)
return img
```
我們在輸出上使用 tanh 激活來將生成的圖像限制在 -1 到 1 的范圍內。
現在已經定義了模型,我們可以看看 GAN 訓練所需的損失函數。
# GAN 損失函數
如前所述,判別器和生成器都有自己的損失函數,這些函數取決于彼此網絡的輸出。 我們可以將 GAN 視為在判別器和生成器之間玩 minimax 游戲,如下所示:

在這里,`D`是我們的判別器,`G`是我們的生成器,`z`是輸入到生成器的隨機向量,`x`是真實圖像。 盡管我們在此處給出了 GAN 損失的總和,但實際上更容易分別考慮這兩種優化。
為了訓練 GAN,我們將在判別器和生成器之間交替進行梯度步驟更新。 在更新判別器時,我們要嘗試使**最大化**判別器做出**正確選擇**的概率。 在更新生成器時,我們想嘗試使**最小化**判別器做出**正確選擇**的可能性。
但是,為了實際實現,我們將與之前給出的內容相比,稍微改變 GAN 損失函數; 這樣做是為了幫助訓練收斂。 變化是,當更新生成器時,而不是**最小化**判別器做出**正確選擇**的可能性; 我們改為**最大化**判別器做出**錯誤選擇**的概率:

更新判別器時,我們嘗試使**最大化**,它對真實數據和偽數據均做出正確選擇的可能性:

# 生成器損失
生成器想要欺騙判別器,換句話說,使判別器輸出`q`用于生成的圖像`G(z)`。 生成器損失只是施加到生成器結果的判別器輸出的二項式交叉熵損失的負值。 請注意,由于生成器始終嘗試生成“真實”圖像,因此交叉熵損失可簡化為:

在這里,每項的含義如下:
* `m`:批量
* `D`:判別器
* `G`:生成器
* `z`:隨機噪聲向量
我們想在訓練 GAN 時最大化損失函數。 當損失最大化時,這意味著生成器能夠生成可能使判別器蒙蔽的圖像,并且判別器針對生成的圖像輸出 1。
# 判別器損失
鑒別者希望能夠區分真實圖像和生成圖像。 它想為真實圖像輸出 1,為生成圖像輸出 0。 判別器損失函數具有以下公式,由于 GAN 訓練和標記的工作方式而再次簡化:

此損失函數有兩項:
* 應用于判別器模型的二項式交叉熵產生了一些真實數據`x`
* 將二項式交叉熵應用于所生成數據`G(z)`的判別器模型結果
如前所述,我們采取這些不利條件,并希望在訓練 GAN 時最大化此損失函數。 當這種損失最大化時,這意味著判別器能夠區分實際輸出和生成的輸出。 注意,當判別器對于真實圖像輸出 1 而對于所生成圖像輸出 0 時,該損失最大。
# 綜合損失
在 TensorFlow 中,我們可以實現整個 GAN 損失,如以下代碼所示。 作為輸入,我們從判別器的輸出中獲取來自生成器的一批偽圖像和來自我們的數據集的一批真實圖像:
```py
def gan_loss(logits_real, logits_fake):
# Target label vectors for generator and discriminator losses.
true_labels = tf.ones_like(logits_real)
fake_labels = tf.zeros_like(logits_fake)
# DISCRIMINATOR loss has 2 parts: how well it classifies real images and how well it
# classifies fake images.
real_image_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_real, labels=true_labels)
fake_image_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_fake, labels=fake_labels)
# Combine and average losses over the batch
discriminator_loss = tf.reduce_mean(real_image_loss + fake_image_loss)
# GENERATOR is trying to make the discriminator output 1 for all its images.
# So we use our target label vector of ones for computing generator loss.
generator_loss = tf.nn.sigmoid_cross_entropy_with_logits(logits=logits_fake, labels=true_labels)
# Average generator loss over the batch.
generator_loss = tf.reduce_mean(G_loss)
return discriminator_loss , generator_loss
```
您可能已經注意到,不可能同時最大化判別器損失和生成器損失。 這就是 GAN 的優點,因為在訓練時,該模型有望達到某種平衡,在這種情況下,生成器必須生成真正高質量的圖像,以欺騙判別器。
TensorFlow 僅允許其優化器最小化而不是最大化。 結果,我們實際上采用了前面所述的損失函數的負值,這意味著我們從最大化損失變為最小化損失。 不過,我們無需執行任何其他操作,因為`tf.nn.sigmoid_cross_entropy_with_logits()`會為我們解決此問題。
# 訓練 GAN
因此,現在有了生成器,判別器和損失函數,剩下的就是訓練! 我們將在 TensorFlow 中給出如何執行此操作的草圖,因為這部分沒有花哨的內容。 就像我們之前所做的那樣,它只是將上一節中的內容以及加載和饋送 MNIST 圖像拼湊在一起。
首先,設置兩個求解器:一個用于判別器,一個用于生成器。 已顯示`AdamOptimizer`的較小值`beta1`,因為它已顯示出可幫助 GAN 訓練收斂:
```py
discriminator_solver = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5)
generator_solver = tf.train.AdamOptimizer(learning_rate=0.001, beta1=0.5)
```
接下來,創建一個隨機噪聲向量; 這可以通過`tf.random_uniform`完成。 這被饋送到生成器網絡以創建一批生成的圖像:
```py
z = tf.random_uniform(maxval=1,minval=-1,shape=[batch_size, dim])
generator_sample = generator(z)
```
然后,我們將一批真實圖像和一批生成的樣本提供給判別器。 我們在這里使用變量范圍來重用我們的模型變量,并確保不創建第二個圖:
```py
with tf.variable_scope("") as scope:
logits_real = discriminator(x)
# We want to re-use the discriminator weights.
scope.reuse_variables()
logits_fake = discriminator(generator_sample )
```
由于需要分別更新它們,因此我們將判別器和生成器的權重分開:
```py
discriminator_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'discriminator')
generator_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'generator')
```
最后,我們計算損失并將其與相關權重一起發送給優化器以進行更新:
```py
discriminator_loss, generator_loss = gan_loss(logits_real, logits_fake)
# Training steps.
discriminator_train_step = discriminator_solver.minimize(discriminator_loss, var_list=discriminator_vars )
generator_train_step = generator_solver.minimize(generator_loss , var_list=generator_vars )
```
這些是訓練 GAN 的主要步驟。 剩下的就是創建一個訓練循環,遍歷大量數據。 如果這樣做,您應該能夠像訓練中那樣輸入任何隨機噪聲向量,并生成圖像。
如下圖所示,創建的圖像開始類似于 MNIST 數字:

# 深度卷積 GAN
**深度卷積 GAN**(**DCGAN**)是我們之前看到的普通 GAN 的擴展。 我們不是使用全連接層,而是使用卷積層。 想法是使用卷積層可以幫助生成器形成更好的圖像。 以下是這種模型的示例:

DCGAN 的示例實現與之前訓練普通 GAN 相同,只是簡單地將判別器和生成器網絡換成一些卷積架構,如以下代碼所示。 請注意,生成器將使用轉置卷積來上采樣:
```py
def discriminator(x):
with tf.variable_scope("discriminator"):
unflatten = tf.reshape(x, shape=[-1, 28, 28, 1])
conv1 = tf.layers.conv2d(inputs=unflatten, kernel_size=5, strides=1, filters=32 ,activation=leaky_relu)
maxpool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=2, strides=2)
conv2 = tf.layers.conv2d(inputs=maxpool1, kernel_size=5, strides=1, filters=64,activation=leaky_relu)
maxpool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=2, strides=2)
flatten = tf.reshape(maxpool2, shape=[-1, 1024])
fc1 = tf.layers.dense(inputs=flatten, units=1024, activation=leaky_relu)
logits = tf.layers.dense(inputs=fc1, units=1)
return logits
```
```py
def generator(z):
with tf.variable_scope("generator"):
fc1 = tf.layers.dense(inputs=z, units=1024, activation=tf.nn.relu)
bn1 = tf.layers.batch_normalization(inputs=fc1, training=True)
fc2 = tf.layers.dense(inputs=bn1, units=7*7*128, activation=tf.nn.relu)
bn2 = tf.layers.batch_normalization(inputs=fc2, training=True)
reshaped = tf.reshape(bn2, shape=[-1, 7, 7, 128])
conv_transpose1 = tf.layers.conv2d_transpose(inputs=reshaped, filters=64, kernel_size=4, strides=2, activation=tf.nn.relu,
padding='same')
bn3 = tf.layers.batch_normalization(inputs=conv_transpose1, training=True)
conv_transpose2 = tf.layers.conv2d_transpose(inputs=bn3, filters=1, kernel_size=4, strides=2, activation=tf.nn.tanh,
padding='same')
img = tf.reshape(conv_transpose2, shape=[-1, 784])
return img
```
只需換出生成器和判別器網絡以使用卷積運算,我們就能生成如下圖像:

現在產生的質量非常好,幾乎與真實數據沒有區別。 另外,請注意,圖像確實非常清晰,并且沒有像我們之前那樣模糊且幾乎沒有偽影。
需要注意的幾點是:
* 對于判別器:再次使用泄漏的 relu,不要使用最大池。 僅使用跨步卷積或平均池。
* 對于生成器:在最后一層使用 relu 和 tanh。
* 通常,最佳實踐是在生成器和判別器上都使用批量規范化層。 它們將始終設置為訓練模式。
* 有時,人們運行生成器優化器的次數是運行判別器優化器的兩倍。
這是一個簡單的 DCGAN 在生成人臉圖像時可以達到的質量的示例:

# WGAN
Wasserstein GAN 是 GAN 的另一種變體,它解決了訓練 GAN 時可能發生的問題,即模式崩潰。 此外,其目的在于給出一種度量,該度量指示 GAN 何時收斂,換句話說,損失函數具有該值的含義。
重要的更改是從損失中刪除對數并修剪判別器權重。
此外,請按照下列步驟操作:
* 訓練判別器比生成器更多
* 減少判別器的權重
* 使用 RMSProp 代替 Adam
* 使用低學習率(0.0005)
WGAN 的缺點是訓練起來較慢:



WGAN 產生的圖像結果仍然不是很好,但是該模型確實有助于解決模式崩潰問題。
# BEGAN
BEGAN 的主要思想是在判別器上使用自編碼器,這將有其自身的損失,該損失會衡量自編碼器對某些圖像(生成器或真實數據)的重構程度:

BEGAN 的一些優點如下:
* 高分辨率(`128x128`)人臉生成(2017 最新技術)。
* 提供一種衡量收斂的方法。
* 即使沒有批量規范和丟棄法也有良好的結果。
* 超參數可控制世代多樣性與質量。 更高的質量也意味著更多的模式崩潰。
* 不需要兩個單獨的優化器。
這是 BEGAN 在生成人臉任務時可以生成的圖像質量的示例:

# 條件 GAN
條件 GAN 是普通 GAN 的擴展,其中判別器和生成器都被設置為某些特定類別`y`。這具有有趣的應用,因為您可以將生成器固定到特定的類,然后使其生成我們選擇的特定同一類的多個不同版本。 例如,如果將`y`設置為與 MNIST 中的數字 7 對應的標簽,則生成器將僅生成 7 的圖像。
使用條件 GAN,minimax 游戲變為:

在這里,我們依賴于額外輸入`y`,它是輸入圖像的類標簽。
合并`x`和`y`,或`z`和`y`的最簡單方法是將它們連接在一起,這樣我們的輸入向量就更長。 這將創建一個更加受控制的數據集擴充系統。 這是 TensorFlow 代碼中的一個示例:

# GAN 的問題
GAN 當前最大的問題是,它們很難訓練。 幸運的是,有一些技術可以使事情變得容易,這是目前非常活躍的研究領域。
在這里,我們將介紹訓練 GAN 的一些問題以及如何解決它們。
# 損失可解釋性
訓練 GAN 時的問題之一是,生成器損失和判別器損失的值都沒有明顯的影響。 這不像訓練分類器,只是等待損失下降以查看模型是否在訓練。
對于 GAN,損失值的下降并不一定意味著該模型正在訓練中:

通過許多人的實驗和研究,以下是有關如何使用 GAN 損失值的一些提示:
* 您不希望判別器的損失下降得很快,因為它將無法向生成器提供反饋以改善它。
* 如果生成器損失迅速下降,則意味著它發現了一個判別器弱點,并一次又一次地利用了這一弱點。 如果發生這種情況,則稱為**模式折疊**。
損失實際上僅對查看訓練中是否出現問題有好處。 因此,沒有很好的方法知道訓練已經收斂。 通常,最好的辦法是繼續查看生成器的輸出。 確保輸出看起來與您的期望接近,并且輸出種類豐富。
# 模式崩潰
這可能是您在訓練 GAN 時遇到的第一個問題。 當生成器找到一組特定的輸入來欺騙判別器時,就會發生模式崩潰,并且它會繼續利用這種故障情況并將潛伏`Z`空間中的許多值折疊為相同的值。
解決此問題的一種方法是使用“小批量功能”或“展開 GANs”,或者完全停止訓練,然后在生成器開始創建非常狹窄的輸出分布時重新開始:


# 改善 GAN 的可訓練性的技術
在這里,我們將介紹一些在訓練 GAN 時使生活更輕松的技術:
* 歸一化輸入到 -1/1 之間
* 使用 BatchNorm
* 使用 Leaky Relu(判別器)
* 在生成器輸出上使用 Relu(生成器),tanh
* 對于下采樣,請使用平均池化或跨步卷積
* 使用 Adam 優化器
* 如果判別器損失迅速下降,則說明存在問題
* 在生成器上使用壓降(在訓練階段和測試階段)
# 小批量判別器
用于改善模式崩潰的一些技術如下:
* 取得判別器某層的輸出
* 將判別器的輸入重塑為矩陣
* 計算 L1 距離
* 計算 L1 距離的指數和
* 將結果連接到輸入(判別器的某些層)


# 總結
在本章中,我們了解了生成模型及其與判別模型的不同之處。 我們還討論了各種自編碼器,包括深度,變體和卷積。 此外,我們了解了一種新型的生成模型,稱為生成對抗網絡(GAN)。 在了解了所有這些生成模型之后,我們看到了如何在 TensorFlow 中自己訓練它們以生成手寫數字,并看到了它們可以產生的不同質量的圖像。
在第 7 章,“遷移學習”中,我們將學習遷移學習及其如何幫助我們加快訓練速度。
- TensorFlow 1.x 深度學習秘籍
- 零、前言
- 一、TensorFlow 簡介
- 二、回歸
- 三、神經網絡:感知器
- 四、卷積神經網絡
- 五、高級卷積神經網絡
- 六、循環神經網絡
- 七、無監督學習
- 八、自編碼器
- 九、強化學習
- 十、移動計算
- 十一、生成模型和 CapsNet
- 十二、分布式 TensorFlow 和云深度學習
- 十三、AutoML 和學習如何學習(元學習)
- 十四、TensorFlow 處理單元
- 使用 TensorFlow 構建機器學習項目中文版
- 一、探索和轉換數據
- 二、聚類
- 三、線性回歸
- 四、邏輯回歸
- 五、簡單的前饋神經網絡
- 六、卷積神經網絡
- 七、循環神經網絡和 LSTM
- 八、深度神經網絡
- 九、大規模運行模型 -- GPU 和服務
- 十、庫安裝和其他提示
- TensorFlow 深度學習中文第二版
- 一、人工神經網絡
- 二、TensorFlow v1.6 的新功能是什么?
- 三、實現前饋神經網絡
- 四、CNN 實戰
- 五、使用 TensorFlow 實現自編碼器
- 六、RNN 和梯度消失或爆炸問題
- 七、TensorFlow GPU 配置
- 八、TFLearn
- 九、使用協同過濾的電影推薦
- 十、OpenAI Gym
- TensorFlow 深度學習實戰指南中文版
- 一、入門
- 二、深度神經網絡
- 三、卷積神經網絡
- 四、循環神經網絡介紹
- 五、總結
- 精通 TensorFlow 1.x
- 一、TensorFlow 101
- 二、TensorFlow 的高級庫
- 三、Keras 101
- 四、TensorFlow 中的經典機器學習
- 五、TensorFlow 和 Keras 中的神經網絡和 MLP
- 六、TensorFlow 和 Keras 中的 RNN
- 七、TensorFlow 和 Keras 中的用于時間序列數據的 RNN
- 八、TensorFlow 和 Keras 中的用于文本數據的 RNN
- 九、TensorFlow 和 Keras 中的 CNN
- 十、TensorFlow 和 Keras 中的自編碼器
- 十一、TF 服務:生產中的 TensorFlow 模型
- 十二、遷移學習和預訓練模型
- 十三、深度強化學習
- 十四、生成對抗網絡
- 十五、TensorFlow 集群的分布式模型
- 十六、移動和嵌入式平臺上的 TensorFlow 模型
- 十七、R 中的 TensorFlow 和 Keras
- 十八、調試 TensorFlow 模型
- 十九、張量處理單元
- TensorFlow 機器學習秘籍中文第二版
- 一、TensorFlow 入門
- 二、TensorFlow 的方式
- 三、線性回歸
- 四、支持向量機
- 五、最近鄰方法
- 六、神經網絡
- 七、自然語言處理
- 八、卷積神經網絡
- 九、循環神經網絡
- 十、將 TensorFlow 投入生產
- 十一、更多 TensorFlow
- 與 TensorFlow 的初次接觸
- 前言
- 1.?TensorFlow 基礎知識
- 2. TensorFlow 中的線性回歸
- 3. TensorFlow 中的聚類
- 4. TensorFlow 中的單層神經網絡
- 5. TensorFlow 中的多層神經網絡
- 6. 并行
- 后記
- TensorFlow 學習指南
- 一、基礎
- 二、線性模型
- 三、學習
- 四、分布式
- TensorFlow Rager 教程
- 一、如何使用 TensorFlow Eager 構建簡單的神經網絡
- 二、在 Eager 模式中使用指標
- 三、如何保存和恢復訓練模型
- 四、文本序列到 TFRecords
- 五、如何將原始圖片數據轉換為 TFRecords
- 六、如何使用 TensorFlow Eager 從 TFRecords 批量讀取數據
- 七、使用 TensorFlow Eager 構建用于情感識別的卷積神經網絡(CNN)
- 八、用于 TensorFlow Eager 序列分類的動態循壞神經網絡
- 九、用于 TensorFlow Eager 時間序列回歸的遞歸神經網絡
- TensorFlow 高效編程
- 圖嵌入綜述:問題,技術與應用
- 一、引言
- 三、圖嵌入的問題設定
- 四、圖嵌入技術
- 基于邊重構的優化問題
- 應用
- 基于深度學習的推薦系統:綜述和新視角
- 引言
- 基于深度學習的推薦:最先進的技術
- 基于卷積神經網絡的推薦
- 關于卷積神經網絡我們理解了什么
- 第1章概論
- 第2章多層網絡
- 2.1.4生成對抗網絡
- 2.2.1最近ConvNets演變中的關鍵架構
- 2.2.2走向ConvNet不變性
- 2.3時空卷積網絡
- 第3章了解ConvNets構建塊
- 3.2整改
- 3.3規范化
- 3.4匯集
- 第四章現狀
- 4.2打開問題
- 參考
- 機器學習超級復習筆記
- Python 遷移學習實用指南
- 零、前言
- 一、機器學習基礎
- 二、深度學習基礎
- 三、了解深度學習架構
- 四、遷移學習基礎
- 五、釋放遷移學習的力量
- 六、圖像識別與分類
- 七、文本文件分類
- 八、音頻事件識別與分類
- 九、DeepDream
- 十、自動圖像字幕生成器
- 十一、圖像著色
- 面向計算機視覺的深度學習
- 零、前言
- 一、入門
- 二、圖像分類
- 三、圖像檢索
- 四、對象檢測
- 五、語義分割
- 六、相似性學習
- 七、圖像字幕
- 八、生成模型
- 九、視頻分類
- 十、部署
- 深度學習快速參考
- 零、前言
- 一、深度學習的基礎
- 二、使用深度學習解決回歸問題
- 三、使用 TensorBoard 監控網絡訓練
- 四、使用深度學習解決二分類問題
- 五、使用 Keras 解決多分類問題
- 六、超參數優化
- 七、從頭開始訓練 CNN
- 八、將預訓練的 CNN 用于遷移學習
- 九、從頭開始訓練 RNN
- 十、使用詞嵌入從頭開始訓練 LSTM
- 十一、訓練 Seq2Seq 模型
- 十二、深度強化學習
- 十三、生成對抗網絡
- TensorFlow 2.0 快速入門指南
- 零、前言
- 第 1 部分:TensorFlow 2.00 Alpha 簡介
- 一、TensorFlow 2 簡介
- 二、Keras:TensorFlow 2 的高級 API
- 三、TensorFlow 2 和 ANN 技術
- 第 2 部分:TensorFlow 2.00 Alpha 中的監督和無監督學習
- 四、TensorFlow 2 和監督機器學習
- 五、TensorFlow 2 和無監督學習
- 第 3 部分:TensorFlow 2.00 Alpha 的神經網絡應用
- 六、使用 TensorFlow 2 識別圖像
- 七、TensorFlow 2 和神經風格遷移
- 八、TensorFlow 2 和循環神經網絡
- 九、TensorFlow 估計器和 TensorFlow HUB
- 十、從 tf1.12 轉換為 tf2
- TensorFlow 入門
- 零、前言
- 一、TensorFlow 基本概念
- 二、TensorFlow 數學運算
- 三、機器學習入門
- 四、神經網絡簡介
- 五、深度學習
- 六、TensorFlow GPU 編程和服務
- TensorFlow 卷積神經網絡實用指南
- 零、前言
- 一、TensorFlow 的設置和介紹
- 二、深度學習和卷積神經網絡
- 三、TensorFlow 中的圖像分類
- 四、目標檢測與分割
- 五、VGG,Inception,ResNet 和 MobileNets
- 六、自編碼器,變分自編碼器和生成對抗網絡
- 七、遷移學習
- 八、機器學習最佳實踐和故障排除
- 九、大規模訓練
- 十、參考文獻