<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 十七、使用自編碼器和 GAN 做表征學習和生成式學習 > 譯者:[@SeanCheney](https://www.jianshu.com/u/130f76596b02) 自編碼器是能夠在無監督(即,訓練集是未標記)的情況下學習輸入數據的緊密表征(叫做潛在表征或編碼)的人工神經網絡。這些編碼通常具有比輸入數據低得多的維度,使得自編碼器對降維有用(參見第 8 章)。自編碼器還可以作為強大的特征檢測器,它們可以用于無監督的深度神經網絡預訓練(正如我們在第 11 章中討論過的)。最后,一些自編碼器是生成式模型:他們能夠隨機生成與訓練數據非常相似的新數據。例如,您可以在臉圖片上訓練自編碼器,然后可以生成新臉。但是生成出來的圖片通常是模糊且不夠真實。 相反,用對抗生成網絡(GAN)生成的人臉可以非常逼真,甚至讓人認為他們是真實存在的人。你可以去這個網址[*https://thispersondoesnotexist.com/*](https://links.jianshu.com/go?to=https%3A%2F%2Fthispersondoesnotexist.com%2F),這是用 StyleGAN 生成的人臉,自己判斷一下(還可以去[*https://thisrentaldoesnotexist.com/*](https://links.jianshu.com/go?to=https%3A%2F%2Fthisrentaldoesnotexist.com%2F),看看 GAN 生成的臥室圖片),GAN 現在廣泛用于超清圖片涂色,圖片編輯,將草圖變為照片,增強數據集,生成其它類型的數據(比如文本、音頻、時間序列),找出其它模型的缺點并強化,等等。 自編碼器和 GAN 都是無監督的,都可以學習緊密表征,都可以用作生成模型,有許多相似的應用,但原理非常不同: * 自編碼器是通過學習,將輸入復制到輸出。聽起來很簡單,但內部結構會使其相當困難。例如,你可以限制潛在表征的大小,或者可以給輸入添加噪音,訓練模型恢復原始輸入。這些限制組織自編碼器直接將輸入復制到輸出,可以強迫模型學習數據的高效表征。總而言之,編碼是自編碼器在一些限制下學習恒等函數的副產品。 * GAN 包括兩個神經網絡:一個生成器嘗試生成和訓練數據相似的數據,一個判別器來區分真實數據和假數據。特別之處在于,生成器和判別器在訓練過程中彼此競爭:生成器就像一個制造偽鈔的罪犯,而判別器就像警察一樣,要把真錢挑出來。對抗訓練(訓練競爭神經網絡),被認為是近幾年的一大進展。在 2016 年,Yann LeCun 甚至說 GAN 是過去 10 年機器學習領域最有趣的發明。 本章中,我們先探究自編碼器的工作原理開始,如何做降維、特征提取、無監督預訓練將、如何用作生成式模型。然后過渡到 GAN。先用 GAN 生成假圖片,可以看到訓練很困難。會討論對抗訓練的主要難點,以及一些解決方法。先從自編碼器開始。 ## 有效的數據表征 以下哪一個數字序列更容易記憶? * 40, 27, 25, 36, 81, 57, 10, 73, 19, 68 * 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14 乍一看,第一個序列似乎應該更容易,因為它要短得多。 但是,如果仔細觀察第二個序列,就會發現它是從 50 到 14 的偶數。一旦你注意到這個規律,第二個序列比第一個更容易記憶,因為你只需要記住規律就成,開始的數字和結尾的數字。請注意,如果您可以快速輕松地記住非常長的序列,則不會在意第二個序列中存在的規律。 只要記住每一個數字,就夠了。 事實上,很難記住長序列,因此識別規律非常有用,并且希望能夠澄清為什么在訓練過程中限制自編碼器會促使它發現并利用數據中的規律。 記憶、感知和模式匹配之間的關系在 20 世紀 70 年代早期由 William Chase 和 Herbert Simon 研究。 他們觀察到,專業棋手能夠通過觀看棋盤 5 秒鐘就能記住所有棋子的位置,這是大多數人認為不可能完成的任務。 然而,只有當這些棋子被放置在現實位置(來自實際比賽)時才是這種情況,而不是隨機放置棋子。 國際象棋專業棋手沒有比你更好的記憶,他們只是更容易看到國際象棋的規律,這要歸功于他們的比賽經驗。 觀察規律有助于他們有效地存儲信息。 就像這個記憶實驗中的象棋棋手一樣,一個自編碼器會查看輸入信息,將它們轉換為高效的潛在表征,然后輸出一些(希望)看起來非常接近輸入的東西。 自編碼器總是由兩部分組成:將輸入轉換為潛在表征的編碼器(或識別網絡),然后是將潛在表征轉換為輸出的解碼器(或生成網絡)(見圖 17-1)。 ![](https://img.kancloud.cn/c6/62/c662a31b37b67b694de59a38fdfbae54_1440x869.png)圖 17-1 記憶象棋試驗(左)和一個簡單的自編碼器(右) 如你所見,自編碼器通常具有與多層感知器(MLP,請參閱第 10 章)相同的體系結構,但輸出層中的神經元數量必須等于輸入數量。 在這個例子中,只有一個由兩個神經元(編碼器)組成的隱藏層和一個由三個神經元(解碼器)組成的輸出層。由于自編碼器試圖重構輸入,所以輸出通常被稱為重建,并且損失函數包含重建損失,當重建與輸入不同時,重建損失會對模型進行懲罰。 由于內部表征具有比輸入數據更低的維度(它是 2D 而不是 3D),所以自編碼器被認為是不完整的。 不完整的自編碼器不能簡單地將其輸入復制到編碼,但它必須找到一種方法來輸出其輸入的副本。 它被迫學習輸入數據中最重要的特征(并刪除不重要的特征)。 我們來看看如何實現一個非常簡單的不完整的自編碼器,以降低維度。 ## 用不完整的線性自編碼器來做 PCA 如果自編碼器僅使用線性激活并且損失函數是均方誤差(MSE),最終其實是做了主成分分析(參見第 8 章)。 以下代碼創建了一個簡單的線性自編碼器,以在 3D 數據集上執行 PCA,并將其投影到 2D: ```py from tensorflow import keras encoder = keras.models.Sequential([keras.layers.Dense(2, input_shape=[3])]) decoder = keras.models.Sequential([keras.layers.Dense(3, input_shape=[2])]) autoencoder = keras.models.Sequential([encoder, decoder]) autoencoder.compile(loss="mse", optimizer=keras.optimizers.SGD(lr=0.1)) ``` 這段代碼與我們在前面章節中創建的所有 MLP 沒有什么大不同。只有以下幾點要注意: * 自編碼器由兩部分組成:編碼器和解碼器。兩者都是常規的`Sequential`模型,每個含有一個緊密層,自編碼器是一個編碼器和解碼器連起來的`Sequential`模型(模型可以用作其它模型中的層)。 * 自編碼器的輸出等于輸入。 * 簡單 PCA 不需要激活函數(即,所有神經元是線性的),且損失函數是 MSE。后面會看到更復雜的自編碼器。 現在用生成出來的 3D 數據集訓練模型,并用模型編碼數據集(即將其投影到 2D): ```py history = autoencoder.fit(X_train, X_train, epochs=20) codings = encoder.predict(X_train) ``` 注意,`X_train`既用來做輸入,也用來做目標。圖 17-2 顯示了原始 3D 數據集(左側)和自編碼器隱藏層的輸出(即編碼層,右側)。 可以看到,自編碼器找到了投影數據的最佳二維平面,保留了數據的盡可能多的差異(就像 PCA 一樣)。 ![](https://img.kancloud.cn/74/57/7457f670a7ce991bbecd224d8bd0a7ca_1440x535.png)圖 17-2 用不完整的線性自編碼器實現 PCA > 筆記:可以將自編碼器當做某種形式的自監督學習(帶有自動生成標簽功能的監督學習,這個例子中標簽等于輸入) ## 棧式自編碼器 就像我們討論過的其他神經網絡一樣,自編碼器可以有多個隱藏層。 在這種情況下,它們被稱為棧式自編碼器(或深度自編碼器)。 添加更多層有助于自編碼器了解更復雜的編碼。 但是,必須注意不要讓自編碼器功能太強大。 設想一個編碼器非常強大,只需學習將每個輸入映射到一個任意數字(并且解碼器學習反向映射)即可。 很明顯,這樣的自編碼器將完美地重構訓練數據,但它不會在過程中學習到任何有用的數據表征(并且它不可能很好地泛化到新的實例)。 棧式自編碼器的架構以中央隱藏層(編碼層)為中心通常是對稱的。 簡單來說,它看起來像一個三明治。 例如,一個用于 MNIST 的自編碼器(在第 3 章中介紹)可能有 784 個輸入,其次是一個隱藏層,有 100 個神經元,然后是一個中央隱藏層,有 30 個神經元,然后是另一個隱藏層,有 100 個神經元,輸出層有 784 個神經元。 這個棧式自編碼器如圖 17-3 所示。 ![](https://img.kancloud.cn/08/bc/08bc4817a1aadd9d0465488a8cbf9739_1440x741.png)圖 17-3 棧式自編碼器 ### 用 Keras 實現棧式自編碼器 你可以像常規深度 MLP 一樣實現棧式自編碼器。 特別是,我們在第 11 章中用于訓練深度網絡的技術也可以應用。例如,下面的代碼使用 SELU 激活函數為 Fashion MNIST 創建了一個棧式自編碼器: ```py stacked_encoder = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), keras.layers.Dense(100, activation="selu"), keras.layers.Dense(30, activation="selu"), ]) stacked_decoder = keras.models.Sequential([ keras.layers.Dense(100, activation="selu", input_shape=[30]), keras.layers.Dense(28 * 28, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) stacked_ae = keras.models.Sequential([stacked_encoder, stacked_decoder]) stacked_ae.compile(loss="binary_crossentropy", optimizer=keras.optimizers.SGD(lr=1.5)) history = stacked_ae.fit(X_train, X_train, epochs=10, validation_data=[X_valid, X_valid]) ``` 逐行看下這個代碼: * 和之前一樣,自編碼器包括兩個子模塊:編碼器和解碼器。 * 編碼器接收 28 × 28 像素的灰度圖片,打平為大小等于 784 的矢量,用兩個緊密層來處理,兩個緊密層都是用 SELU 激活函數(還可以加上 LeCun 歸一初始化,但因為網絡不深,效果不大)。對于每張輸入圖片,編碼器輸出的矢量大小是 30。 * 解碼器接收大小等于 30 的編碼(編碼器的輸出),用兩個緊密層來處理,最后的矢量轉換為 28 × 28 的數組,使解碼器的輸出和編碼器的輸入形狀相同。 * 編譯時,使用二元交叉熵損失,而不是 MSE。將重建任務當做多標簽分類問題:每個像素強度表示像素應該為黑色的概率。這么界定問題(而不是當做回歸問題),可以使模型收斂更快。 * 最后,使用`X_train`既作為輸入,也作為目標,來訓練模型(相似的,使用`X_valid`既作為驗證的輸入也作為目標)。 ### 可視化重建 確保自編碼器訓練得當的方式之一,是比較輸入和輸出:差異不應過大。畫一些驗證集的圖片,及其重建: ```py def plot_image(image): plt.imshow(image, cmap="binary") plt.axis("off") def show_reconstructions(model, n_images=5): reconstructions = model.predict(X_valid[:n_images]) fig = plt.figure(figsize=(n_images * 1.5, 3)) for image_index in range(n_images): plt.subplot(2, n_images, 1 + image_index) plot_image(X_valid[image_index]) plt.subplot(2, n_images, 1 + n_images + image_index) plot_image(reconstructions[image_index]) show_reconstructions(stacked_ae) ``` 圖 17-4 展示了比較結果。 ![](https://img.kancloud.cn/f7/08/f70899b6306b333628389e020a760873_1439x514.png)圖 17-4 原始圖片(上)及其重建(下) 可以認出重建,但圖片有些失真。需要再訓練模型一段時間,或使編碼器和解碼器更深,或使編碼更大。但如果使網絡太強大,就學不到數據中的規律。 ### 可視化 Fashion MNIST 數據集 訓練好棧式自編碼器之后,就可以用它給數據集降維了。可視化的話,結果不像(第 8 章其它介紹的)其它降維方法那么好,但自編碼器的優勢是可以處理帶有多個實例多個特征的大數據集。所以一個策略是利用自編碼器將數據集降維到一個合理的水平,然后使用另外一個降維算法做可視化。用這個策略來可視化 Fashion MNIST。首先,使用棧式自編碼器的編碼器將維度降到 30,然后使用 Scikit-Learn 的 t-SNE 算法實現,將維度降到 2 并做可視化: ```py from sklearn.manifold import TSNE X_valid_compressed = stacked_encoder.predict(X_valid) tsne = TSNE() X_valid_2D = tsne.fit_transform(X_valid_compressed) ``` 對數據集作圖: ```py plt.scatter(X_valid_2D[:, 0], X_valid_2D[:, 1], c=y_valid, s=10, cmap="tab10") ``` 圖 17-5 展示了結果的散點圖(并展示了一些圖片)。t-SNE 算法區分除了幾類,比較符合圖片的類別(每個類的顏色不一樣)。 ![](https://img.kancloud.cn/34/ef/34ef37ebde85e132d74d512a2dca129b_1441x1144.png)圖 17-5 使用自編碼器和 t-SNE 對 Fashion MNIST 做可視化 自編碼器的另一個用途是無監督預訓練。 ### 使用棧式自編碼器做無監督預訓練 第 11 章討論過,如果要處理一個復雜的監督任務,但又缺少標簽數據,解決的方法之一,是找一個做相似任務的神經網絡,復用它的底層。這么做就可以使用少量訓練數據訓練出高性能的模型,因為模型不必學習所有低層次特征;模型可以復用之前的特征探測器。 相似的,如果有一個大數據集,但大部分實例是無標簽的,可以用全部數據訓練一個棧式自編碼器,然后使用其底層創建一個神經網絡,再用有標簽數據來訓練。例如,圖 17-6 展示了如何使用棧式自編碼器來做分類的無監督預訓練。當訓練分類器時,如果標簽數據不足,可以凍住預訓練層(底層)。 ![](https://img.kancloud.cn/86/3c/863cb4d2595280ef734f4aa5009559e0_1440x877.png)圖 17-6 使用自編碼器做無監督預訓練 > 筆記:無標簽數據很多,有標簽數據數據很少,非常普遍。搭建一個大無便簽數據集很便宜(比如,一段小腳本可以從網上下載許多圖片),但是給這些圖片打標簽(比如,將其標簽為可愛或不可愛)只有人做才靠譜。打標簽又耗時又耗錢,所以人工標注實例有幾千就不錯了。 代碼實現沒有特殊之處:用所有訓練數據訓練自編碼器,然后用編碼器層創建新的神經網絡(本章有練習題例子)。 接下來,看看關聯權重的方法。 ### 關聯權重 當自編碼器整齊地對稱時,就像我們剛剛構建的那樣,一種常用方法是將解碼器層的權重與編碼器層的權重相關聯。 這樣減半了模型中的權重數量,加快了訓練速度,并限制了過度擬合的風險。具體來說,如果自編碼器總共具有`N`個層(不算輸入層),并且 W<sub>L</sub> 表示第 L<sup>th</sup>層的連接權重(例如,層 1 是第一隱藏層,則層`N / 2`是編碼層,而層`N`是輸出層),則解碼器層權重可以簡單地定義為:W<sub>N–L+1</sub> = W<sub>L</sub><sup>T</sup>(其中 L = 1, 2, ..., N/2)。 使用 Keras 將層的權重關聯起來,先定義一個自定義層: ```py class DenseTranspose(keras.layers.Layer): def __init__(self, dense, activation=None, **kwargs): self.dense = dense self.activation = keras.activations.get(activation) super().__init__(**kwargs) def build(self, batch_input_shape): self.biases = self.add_weight(name="bias", initializer="zeros", shape=[self.dense.input_shape[-1]]) super().build(batch_input_shape) def call(self, inputs): z = tf.matmul(inputs, self.dense.weights[0], transpose_b=True) return self.activation(z + self.biases) ``` 自定義層的作用就像一個常規緊密層,但使用了另一個緊密層的權重,并且做了轉置(設置`transpose_b=True`等同于轉置第二個參數,但在`matmul()`運算中實時做轉置更為高效)。但是,要使用自己的偏置矢量。然后,創建一個新的棧式自編碼器,將解碼器的緊密層和編碼器的緊密層關聯起來: ```py dense_1 = keras.layers.Dense(100, activation="selu") dense_2 = keras.layers.Dense(30, activation="selu") tied_encoder = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), dense_1, dense_2 ]) tied_decoder = keras.models.Sequential([ DenseTranspose(dense_2, activation="selu"), DenseTranspose(dense_1, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) tied_ae = keras.models.Sequential([tied_encoder, tied_decoder]) ``` 這個模型的重建誤差小于前一個模型,且參數量只有一半。 ### 一次訓練一個自編碼器 不是一次完成整個棧式自編碼器的訓練,而是一次訓練一個淺自編碼器,然后將所有這些自編碼器堆疊到一個棧式自編碼器(因此名稱)中,通常要快得多,如圖 17-7 所示。 這個方法如今用的不多了,但偶爾還會撞見談到“貪婪層級訓練”的論文,所以還是看一看。 ![](https://img.kancloud.cn/90/d1/90d19d7e5811ecd77c981dbce55fdac1_1741x742.png)圖 17-7 一次訓練一個自編碼器 在訓練的第一階段,第一個自編碼器學習重構輸入。 然后,使用整個訓練集訓練第一個自編碼器,得到一個新的(壓縮過的)訓練集。然后用這個數據集訓練第二個自編碼器。這是第二階段的訓練。最后,我們用所有這些自編碼器創建一個三明治結構,見圖 17-7(即,先把每個自編碼器的隱藏層疊起來,再加上輸出層)。這樣就得到了最終的棧式自編碼器(見 notebook)。我們可以用這種方式訓練更多的自編碼器,搭建非常深的棧式自編碼器。 正如前面討論過的,現在的一大趨勢是 Geoffrey Hinton 等人在 2006 年發現的,靠這種貪婪層級方法,可以用無監督方式訓練神經網絡。他們還使用了受限玻爾茲曼機(RBM,見附錄 E)。但在 2007 年,Yoshua Bengio 發現只用自編碼器也可以達到不錯的效果。在這幾年間,自編碼器是唯一的有效訓練深度網絡的方法,知道出現第 11 章介紹過的方法。 自編碼器不限于緊密網絡:還有卷積自編碼器和循環自編碼器。 ## 卷積自編碼器 如果處理的是圖片,則前面介紹的自編碼器的效果可能一般(除非圖片非常小)。第 14 章介紹過,對于圖片任務,卷積神經網絡比緊密網絡的效果更好。所以如果想用自編碼器來處理圖片的話(例如,無監督預訓練或降維),你需要搭建一個卷積自編碼器。編碼器是一個包含卷積層和池化層的常規 CNN。通常降低輸入的空間維度(即,高和寬),同時增加深度(即,特征映射的數量)。解碼器的工作相反(放大圖片,壓縮深度),要這么做的話,可以轉置卷積層(或者,可以將上采樣層和卷積層合并)。下面是一個卷積自編碼器處理 Fashion MNIST 的例子: ```py conv_encoder = keras.models.Sequential([ keras.layers.Reshape([28, 28, 1], input_shape=[28, 28]), keras.layers.Conv2D(16, kernel_size=3, padding="same", activation="selu"), keras.layers.MaxPool2D(pool_size=2), keras.layers.Conv2D(32, kernel_size=3, padding="same", activation="selu"), keras.layers.MaxPool2D(pool_size=2), keras.layers.Conv2D(64, kernel_size=3, padding="same", activation="selu"), keras.layers.MaxPool2D(pool_size=2) ]) conv_decoder = keras.models.Sequential([ keras.layers.Conv2DTranspose(32, kernel_size=3, strides=2, padding="valid", activation="selu", input_shape=[3, 3, 64]), keras.layers.Conv2DTranspose(16, kernel_size=3, strides=2, padding="same", activation="selu"), keras.layers.Conv2DTranspose(1, kernel_size=3, strides=2, padding="same", activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) conv_ae = keras.models.Sequential([conv_encoder, conv_decoder]) ``` ## 循環自編碼器 如果你想用自編碼器處理序列,比如對時間序列或文本無監督學習和降維,則循環神經網絡要優于緊密網絡。搭建循環自編碼器很簡單:編碼器是一個序列到矢量的 RNN,而解碼器是矢量到序列的 RNN: ```py recurrent_encoder = keras.models.Sequential([ keras.layers.LSTM(100, return_sequences=True, input_shape=[None, 28]), keras.layers.LSTM(30) ]) recurrent_decoder = keras.models.Sequential([ keras.layers.RepeatVector(28, input_shape=[30]), keras.layers.LSTM(100, return_sequences=True), keras.layers.TimeDistributed(keras.layers.Dense(28, activation="sigmoid")) ]) recurrent_ae = keras.models.Sequential([recurrent_encoder, recurrent_decoder]) ``` 這個循環自編碼器可以處理任意長度的序列,每個時間步有 28 個維度。這意味著,可以將 Fashion MNIST 的圖片作為幾行序列來處理。注意,解碼器第一層用的是`RepeatVector`,以保證在每個時間步將輸入矢量傳給解碼器。 我們現在已經看過了多種自編碼器(基本的、棧式的、卷積的、循環的),學習了訓練的方法(一次性訓練或逐層訓練)。還學習了兩種應用:視覺可視化和無監督學習。 為了讓自編碼學習特征,我們限制了編碼層的大小(使它處于不完整的狀態)。還可以使用許多其他的限制方法,可以讓編碼層和輸入層一樣大,甚至更大,得到一個過完成的自編碼器。下面就是其中一些方法。 ## 降噪自編碼 另一種強制自編碼器學習特征的方法是為其輸入添加噪聲,對其進行訓練以恢復原始的無噪聲輸入。 自 20 世紀 80 年代以來,使用自編碼器消除噪音的想法已經出現(例如,在 Yann LeCun 的 1987 年碩士論文中提到過)。 在 2008 年的一篇論文中,帕斯卡爾文森特等人。 表明自編碼器也可用于特征提取。 在 2010 年的一篇爐溫中, Vincent 等人引入了棧式降噪自編碼器。 噪聲可以是添加到輸入的純高斯噪聲,或者可以隨機關閉輸入,就像 dropout(在第 11 章介紹)。 圖 17-8 顯示了這兩種方法。 ![](https://img.kancloud.cn/91/0e/910ef476b1a98b90309b11be93ae9fab_1440x881.png)圖 17-8 高斯噪音(左)和 dropout(右)的降噪自編碼器 實現很簡單:常規的棧式自編碼器中有一個應用于輸入的`Dropout`層(或使用`GaussianNoise`層)。`Dropout`層只在訓練中起作用(`GaussianNoise`層也只在訓練中起作用): ```py dropout_encoder = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), keras.layers.Dropout(0.5), keras.layers.Dense(100, activation="selu"), keras.layers.Dense(30, activation="selu") ]) dropout_decoder = keras.models.Sequential([ keras.layers.Dense(100, activation="selu", input_shape=[30]), keras.layers.Dense(28 * 28, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) dropout_ae = keras.models.Sequential([dropout_encoder, dropout_decoder]) ``` 圖 17-9 展示了一些帶有造影的圖片(有一半像素被丟棄),重建圖片是用基于 dropout 的自編碼器實現的。注意自編碼器是如何猜測圖片中不存在的細節的,比如四張圖片的領口。 ![](https://img.kancloud.cn/96/82/9682c40d7bbb6addfe9b4dbef28294b2_1439x564.png)圖 17-9 噪音圖片(上)和重建圖片(下) ## 稀疏自編碼器 通常良好特征提取的另一種約束是稀疏性:通過向損失函數添加適當的項,讓自編碼器減少編碼層中活動神經元的數量。 例如,可以讓編碼層中平均只有 5% 的活躍神經元。 這迫使自編碼器將每個輸入表示為少量激活的組合。 因此,編碼層中的每個神經元通常都會代表一個有用的特征(如果每個月只能說幾個字,你會說的特別精煉)。 使用 sigmoid 激活函數可以實現這個目的。添加一個編碼層(比如,有 300 個神經元),給編碼層的激活函數添加?<sub>1</sub>正則(解碼器就是一個常規解碼器): ```py sparse_l1_encoder = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), keras.layers.Dense(100, activation="selu"), keras.layers.Dense(300, activation="sigmoid"), keras.layers.ActivityRegularization(l1=1e-3) ]) sparse_l1_decoder = keras.models.Sequential([ keras.layers.Dense(100, activation="selu", input_shape=[300]), keras.layers.Dense(28 * 28, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) sparse_l1_ae = keras.models.Sequential([sparse_l1_encoder, sparse_l1_decoder]) ``` `ActivityRegularization`只是返回輸入,但副作用是新增了訓練損失,大小等于輸入的絕對值之和(這個層只在訓練中起作用)。等價的,可以移出`ActivityRegularization`,并在前一層設置`activity_regularizer=keras.regularizers.l1(1e-3)`。這項懲罰可以讓神經網絡產生接近 0 的編碼,如果沒有正確重建輸入,還是會有損失,仍然會產生一些非 0 值。不使用?<sub>2</sub>,而使用?<sub>1</sub>,可以讓神經網絡保存最重要的編碼,同時消除輸入圖片不需要的編碼(而不是壓縮所有編碼)。 另一種結果更好的方法是在每次訓練迭代中測量編碼層的實際稀疏度,當偏移目標值,就懲罰模型。 我們通過計算整個訓練批次中編碼層中每個神經元的平均激活來實現。 批量大小不能太小,否則平均數不準確。 一旦我們對每個神經元進行平均激活,我們希望通過向損失函數添加稀疏損失來懲罰太活躍的神經元,或不夠活躍的神經元。 例如,如果我們測量一個神經元的平均激活值為 0.3,但目標稀疏度為 0.1,那么它必須受到懲罰才能激活更少。 一種方法可以簡單地將平方誤差`(0.3-0.1)^2`添加到損失函數中,但實際上更好的方法是使用 Kullback-Leibler 散度(在第 4 章中簡要討論),它具有比均方誤差更強的梯度,如圖 17-10 所示。 ![](https://img.kancloud.cn/11/58/1158eff28d4d554010c5836826eb4ebb_1440x935.png)圖 17-10 稀疏損失 給定兩個離散的概率分布`P`和`Q`,這些分布之間的 KL 散度,記為 D<sub>KL</sub>(P // Q),可以使用公式 17-1 計算。 ![](https://img.kancloud.cn/ac/9f/ac9fbe50be46f86885e9a11fa226c0e9_816x180.png)公式 17-1 Kullback–Leibler 散度 在我們的例子中,我們想要測量編碼層中的神經元將激活的目標概率`p`與實際概率`q`(即,訓練批次上的平均激活)之間的差異。 所以 KL 散度簡化為公式 17-2。 ![](https://img.kancloud.cn/99/bc/99bcbe70f8320b705685bdfd648f8e3e_1022x176.png)公式 17-2 目標稀疏度 p 和實際稀疏度 q 之間的 KL 散度 一旦我們已經計算了編碼層中每個神經元的稀疏損失,就相加這些損失,并將結果添加到損失函數中。 為了控制稀疏損失和重構損失的相對重要性,我們可以用稀疏權重超參數乘以稀疏損失。 如果這個權重太高,模型會緊貼目標稀疏度,但它可能無法正確重建輸入,導致模型無用。 相反,如果它太低,模型將大多忽略稀疏目標,它不會學習任何有趣的功能。 現在就可以實現基于 KL 散度的稀疏自編碼器了。首先,創建一個自定義正則器來實現 KL 散度正則: ```py K = keras.backend kl_divergence = keras.losses.kullback_leibler_divergence class KLDivergenceRegularizer(keras.regularizers.Regularizer): def __init__(self, weight, target=0.1): self.weight = weight self.target = target def __call__(self, inputs): mean_activities = K.mean(inputs, axis=0) return self.weight * ( kl_divergence(self.target, mean_activities) + kl_divergence(1\. - self.target, 1\. - mean_activities)) ``` 使用`KLDivergenceRegularizer`作為編碼層的激活函數,創建稀疏自編碼器: ```py kld_reg = KLDivergenceRegularizer(weight=0.05, target=0.1) sparse_kl_encoder = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), keras.layers.Dense(100, activation="selu"), keras.layers.Dense(300, activation="sigmoid", activity_regularizer=kld_reg) ]) sparse_kl_decoder = keras.models.Sequential([ keras.layers.Dense(100, activation="selu", input_shape=[300]), keras.layers.Dense(28 * 28, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) sparse_kl_ae = keras.models.Sequential([sparse_kl_encoder, sparse_kl_decoder]) ``` 在 Fashion MNIST 上訓練好稀疏自編碼器之后,編碼層中的神經元的激活大部分接近 0(70%的激活小于 0.1),所有神經元的平均值在 0.1 附近(90%的平均激活在 0.1 和 0.2 之間)見圖 17-11。 ![](https://img.kancloud.cn/d2/54/d2543124fd501a55e70fae4a69bd2278_1439x391.png)圖 17-11 編碼層的所有激活的分布(左)和每個神經元平均激活的分布(右) ## 變分自編碼器(VAE) Diederik Kingma 和 Max Welling 于 2013 年推出了另一類重要的自編碼器,并迅速成為最受歡迎的自編碼器類型之一:變分自編碼器。 它與我們迄今為止討論的所有自編碼器非常不同,特別是: * 它們是概率自編碼器,意味著即使在訓練之后,它們的輸出部分也是偶然確定的(相對于僅在訓練過程中使用隨機性的自編碼器的去噪)。 * 最重要的是,它們是生成自編碼器,這意味著它們可以生成看起來像從訓練集中采樣的新實例。 這兩個屬性使它們與 RBM 非常相似(見附錄 E),但它們更容易訓練,并且取樣過程更快(在 RBM 之前,您需要等待網絡穩定在“熱平衡”之后才能進行取樣一個新的實例)。正如其名字,變分自編碼器要做變分貝葉斯推斷(第 9 章介紹過),這是估計變微分推斷的一種有效方式。 我們來看看他們是如何工作的。 圖 17-12(左)顯示了一個變分自編碼器。 當然,您可以認識到所有自編碼器的基本結構,編碼器后跟解碼器(在本例中,它們都有兩個隱藏層),但有一個轉折點:不是直接為給定的輸入生成編碼 ,編碼器產生平均編碼`μ`和標準差`σ`。 然后從平均值`μ`和標準差`σ`的高斯分布隨機采樣實際編碼。 之后,解碼器正常解碼采樣的編碼。 該圖的右側部分顯示了一個訓練實例通過此自編碼器。 首先,編碼器產生`μ`和`σ`,隨后對編碼進行隨機采樣(注意它不是完全位于`μ`處),最后對編碼進行解碼,最終的輸出與訓練實例類似。 ![](https://img.kancloud.cn/dd/77/dd77c7e6ce00c15bba32e2773e570193_1440x1164.png)圖 17-12 變分自編碼器(左)和一個執行中的實例(右) 從圖中可以看出,盡管輸入可能具有非常復雜的分布,但變分自編碼器傾向于產生編碼,看起來好像它們是從簡單的高斯分布采樣的:在訓練期間,損失函數(將在下面討論)推動 編碼在編碼空間(也稱為潛在空間)內逐漸遷移以占據看起來像高斯點集成的云的大致(超)球形區域。 一個重要的結果是,在訓練了一個變分自編碼器之后,你可以很容易地生成一個新的實例:只需從高斯分布中抽取一個隨機編碼,對它進行解碼就可以了! 再來看看損失函數。 它由兩部分組成。 首先是通常的重建損失,推動自編碼器重現其輸入(我們可以使用交叉熵來解決這個問題,如前所述)。 第二種是潛在的損失,推動自編碼器使編碼看起來像是從簡單的高斯分布中采樣,為此我們使用目標分布(高斯分布)與編碼實際分布之間的 KL 散度。 數學比以前復雜一點,特別是因為高斯噪聲,它限制了可以傳輸到編碼層的信息量(從而推動自編碼器學習有用的特征)。 幸好,這些方程經過簡化,可以用公式 17-3 計算潛在損失: ![](https://img.kancloud.cn/9d/c7/9dc7b9d62fdbd60fe98b883ed03754b4_928x212.png)公式 17-3 變分自編碼器的潛在損失 在這個公式中,L 是潛在損失,n 是編碼維度,μ<sub>i</sub> 和 σ<sub>i</sub>是編碼的第 i<sup>th</sup>個成分的平均值和標準差。矢量 u 和σ是編碼器的輸出,見圖 17-12 的左邊。 一種常見的變體是訓練編碼器輸出`γ= log(σ^2)`而不是`σ`。 可以用公式 17-4 計算潛在損失。這個方法的計算更穩定,且可以加速訓練。 ![](https://img.kancloud.cn/b1/a0/b1a0cf87bd1836ac0cc1e12f7aca08bb_986x218.png)公式 17-4 變分自編碼器的潛在損失,使用γ= log(σ^2) 給 Fashion MNIST 創建一個自編碼器(見圖 17-12,使用γ變體)。首先,需要一個自定義層從編碼采樣,給定μ 和 γ: ```py class Sampling(keras.layers.Layer): def call(self, inputs): mean, log_var = inputs return K.random_normal(tf.shape(log_var)) * K.exp(log_var / 2) + mean ``` 這個`Sampling`層接收兩個輸入:`mean (μ)` 和 `log_var (γ)`。使用函數`K.random_normal()`根據正態分布隨機采樣矢量(形狀為γ)平均值為 0 標準差為 1。然后乘以 exp(γ / 2)(這個值等于σ),最后加上μ并返回結果。這樣就能從平均值為 0 標準差為 1 的正態分布采樣編碼矢量。 然后,創建編碼器,因為模型不是完全順序的,所以要使用 Functional API: ```py codings_size = 10 inputs = keras.layers.Input(shape=[28, 28]) z = keras.layers.Flatten()(inputs) z = keras.layers.Dense(150, activation="selu")(z) z = keras.layers.Dense(100, activation="selu")(z) codings_mean = keras.layers.Dense(codings_size)(z) # μ codings_log_var = keras.layers.Dense(codings_size)(z) # γ codings = Sampling()([codings_mean, codings_log_var]) variational_encoder = keras.Model( inputs=[inputs], outputs=[codings_mean, codings_log_var, codings]) ``` 注意,輸出`codings_mean` (μ)和`codings_log_var` (γ)的`Dense`層,有同樣的輸入(即,第二個緊密層的輸出)。然后將`codings_mean`和`codings_log_var`傳給`Sampling`層。最后,`variational_encoder`模型有三個輸出,可以用來檢查`codings_mean`和`codings_log_var`的值。真正使用的是最后一個(`codings`)。下面創建解碼器: ```py decoder_inputs = keras.layers.Input(shape=[codings_size]) x = keras.layers.Dense(100, activation="selu")(decoder_inputs) x = keras.layers.Dense(150, activation="selu")(x) x = keras.layers.Dense(28 * 28, activation="sigmoid")(x) outputs = keras.layers.Reshape([28, 28])(x) variational_decoder = keras.Model(inputs=[decoder_inputs], outputs=[outputs]) ``` 對于解碼器,因為是簡單棧式結構,可以不使用 Functional API,而使用 Sequential API。最后,創建變分自編碼器: ```py _, _, codings = variational_encoder(inputs) reconstructions = variational_decoder(codings) variational_ae = keras.Model(inputs=[inputs], outputs=[reconstructions]) ``` 注意,我們忽略了編碼器的前兩個輸出。最后,必須將潛在損失和重建損失加起來: ```py latent_loss = -0.5 * K.sum( 1 + codings_log_var - K.exp(codings_log_var) - K.square(codings_mean), axis=-1) variational_ae.add_loss(K.mean(latent_loss) / 784.) variational_ae.compile(loss="binary_crossentropy", optimizer="rmsprop") ``` 我們首先用公式 17-4 計算批次中每個實例的潛在損失。然后計算所有實例的平均損失,然后除以,使其量綱與重建損失一致。實際上,變分自編碼器的重建損失是像素重建誤差的和,但當 Keras 計算`"binary_crossentropy"`損失時,它計算的是 784 個像素的平均值,而不是和。因此,重建損失比真正要的值小 784 倍。我們可以定義一個自定義損失來計算誤差和,但除以 784 更簡單。 注意,這里使用了`RMSprop`優化器。最后,我們可以訓練自編碼器。 ```py history = variational_ae.fit(X_train, X_train, epochs=50, batch_size=128, validation_data=[X_valid, X_valid]) ``` ### 生成 Fashion MNIST 圖片 接下來用上面的變分自編碼器生成圖片。我們要做的只是從高斯分布隨機采樣編碼,然后做解碼: ```py codings = tf.random.normal(shape=[12, codings_size]) images = variational_decoder(codings).numpy() ``` 圖 17-13 展示了 12 張生成的圖片。 ![](https://img.kancloud.cn/2c/84/2c8403b61ebf35594e2b354b8dd28cd2_928x679.png)圖 17-13 用變分自編碼器生成的 Fashion MNIST 圖片 大多數生成的圖片很逼真,就是有些模糊。其它的效果一般,這是因為自編碼器只學習了幾分鐘。經過微調和更長時間的訓練,效果就能編號。 變分自編碼器也可以做語義插值:不是對兩張圖片做像素級插值(結果就像是兩張圖重疊),而是在編碼級插值。先用編碼層運行兩張圖片,然后對兩個編碼層插值,然后解碼插值編碼,得到結果圖片。結果就像一個常規的 Fashion MINIST 圖片,但還是介于原始圖之間。在接下來的代碼中,將 12 個生成出來的編碼,排列成 3 × 4 的網格,然后用 TensorFlow 的`tf.image.resize()`函數,將其縮放為 5 × 7。默認條件下,`resize()`函數會做雙線性插值,所以每兩個行或列都會包含插值編碼。然后用解碼器生成所有圖片: ```py codings_grid = tf.reshape(codings, [1, 3, 4, codings_size]) larger_grid = tf.image.resize(codings_grid, size=[5, 7]) interpolated_codings = tf.reshape(larger_grid, [-1, codings_size]) images = variational_decoder(interpolated_codings).numpy() ``` 圖 17-14 展示了結果。畫框的是原始圖,其余是根據附近圖片做出的語義插值圖。注意,第 4 行第 5 列的鞋,是上下兩張圖的完美融合。 ![](https://img.kancloud.cn/af/f8/aff8e93e3ad153b4f07204cd8858b727_1442x1007.png)圖 17-14 語義插值 變分自編碼器流行幾年之后,就被 GAN 超越了,后者可以生成更為真實的圖片。 ## 對抗生成網絡(GAN) 對抗生成網絡是 Ian Goodfellow 在 2014 年的一篇[論文](https://links.jianshu.com/go?to=https%3A%2F%2Fhoml.info%2Fgan)中提出的,盡管一開始就引起了眾人的興趣,但用了幾年時間才客服了訓練 GAN 的一些難點。和其它偉大的想法一樣,GAN 的本質很簡單:讓神經網絡互相競爭,讓其在競爭中進步。見圖 17-15,GAN 包括兩個神經網絡: * 生成器 使用隨機分布作為輸入(通常為高斯分布),并輸出一些數據,比如圖片。可以將隨機輸入作為生成文件的潛在表征(即,編碼)。生成器的作用和變分自編碼器中的解碼器差不多,可以用同樣的方式生成圖片(只要輸入一些高斯噪音,就能輸出全新的圖片)。但是,生成器的訓練過程很不一樣。 * 判別器 從訓練集取出一張圖片,判斷圖片是真是假。 ![](https://img.kancloud.cn/af/e8/afe81ed9d59f7888106d84658759a335_1442x937.png)圖 17-15 一個對抗生成網絡 在訓練中,生成器和判別器的目標正好相反:判別器判斷圖片的真假,生成器盡力生成看起來像真圖的圖片。因為 GAN 由這兩個目的不同的網絡組成,所以不能像常規網絡那樣訓練。每次訓練迭代分成兩個階段: * 第一個階段,訓練判別器。從訓練集取樣一批真實圖片,數量與假圖片相同。假圖片的標簽設為 0,真圖片的標簽設為 1,判別器用這個有標簽的批次訓練一步,使用二元交叉熵損失。反向傳播在這一階段只優化判別器的權重。 * 第二個階段,訓練生成器。首先用生成器產生另一個批次的假圖片,再用判別器來判斷圖片是真是假。這一次不添加真圖片,但所有標簽都設為 1(真):換句話說,我們想讓生成器產生可以讓判別器信以為真的圖片。判別器的權重在這一步是冷凍的,所以反向傳播只影響生成器。 > 筆記:生成器看不到真圖,但卻逐漸生成出逼真的不騙。它只是使用了經過判別器返回的梯度。幸好,隨著判別器的優化,這些二手梯度中包含的關于真圖的信息也越來越多,所以生成器才能進步。 接下來為 Fashion MNIST 創建一個簡單的 GAN 模型。 首先,創建生成器和判別器。生成器很像自編碼器的解碼器,判別器就是一個常規的二元分類器(圖片作為輸入,輸出是包含一個神經元的緊密層,使用 sigmoid 激活函數)。對于每次訓練迭代中的第二階段,需要完整的 GAN 模型: ```py codings_size = 30 generator = keras.models.Sequential([ keras.layers.Dense(100, activation="selu", input_shape=[codings_size]), keras.layers.Dense(150, activation="selu"), keras.layers.Dense(28 * 28, activation="sigmoid"), keras.layers.Reshape([28, 28]) ]) discriminator = keras.models.Sequential([ keras.layers.Flatten(input_shape=[28, 28]), keras.layers.Dense(150, activation="selu"), keras.layers.Dense(100, activation="selu"), keras.layers.Dense(1, activation="sigmoid") ]) gan = keras.models.Sequential([generator, discriminator]) ``` 然后,我們需要編譯這些模型。因為判別器是一個二元分類器,我們可以使用二元交叉熵損失。生成器只能通過 GAN 訓練,所以不需要編譯生成器。`gan`模型也是一個二元分類器,所以可以使用二元交叉熵損失。重要的,不能在第二個階段訓練判別器,所以編譯模型之前,使其不可訓練: ```py discriminator.compile(loss="binary_crossentropy", optimizer="rmsprop") discriminator.trainable = False gan.compile(loss="binary_crossentropy", optimizer="rmsprop") ``` > 筆記:Keras 只有在編譯模型時才會考慮`trainable`屬性,所以運行這段代碼后,如果調用`fit()`方法或`train_on_batch()`方法,`discriminator`就是可訓練的了。但在`gan`模型上調用這些方法,判別器是不可訓練的。 因為訓練循環是非常規的,我們不能使用常規的`fit()`方法。但我們可以寫一個自定義的訓練循環。要這么做,需要先創建一個`Dataset`迭代這些圖片: ```py batch_size = 32 dataset = tf.data.Dataset.from_tensor_slices(X_train).shuffle(1000) dataset = dataset.batch(batch_size, drop_remainder=True).prefetch(1) ``` 現在就可以來寫訓練循環了。用`train_gan()`函數來包裝: ```py def train_gan(gan, dataset, batch_size, codings_size, n_epochs=50): generator, discriminator = gan.layers for epoch in range(n_epochs): for X_batch in dataset: # phase 1 - training the discriminator noise = tf.random.normal(shape=[batch_size, codings_size]) generated_images = generator(noise) X_fake_and_real = tf.concat([generated_images, X_batch], axis=0) y1 = tf.constant([[0.]] * batch_size + [[1.]] * batch_size) discriminator.trainable = True discriminator.train_on_batch(X_fake_and_real, y1) # phase 2 - training the generator noise = tf.random.normal(shape=[batch_size, codings_size]) y2 = tf.constant([[1.]] * batch_size) discriminator.trainable = False gan.train_on_batch(noise, y2) train_gan(gan, dataset, batch_size, codings_size) ``` 和前面討論的一樣,每次迭代都有兩個階段: * 在第一階段,向生成器輸入高斯噪音來生成假圖片,然后再補充同等數量的真圖片。假圖片的目標`y1`設為 0,真圖片的目標`y1`設為 1。然后用這個批次訓練判別器。注意,將判別器的`trainable`屬性設為`True`:這是為了避免 Keras 檢查到現在是`False`而在訓練時為`True`,顯示警告。 * 在第二階段,向 GAN 輸入一些高斯噪音。它的生成器會開始假圖片,然后判別器會判斷其真假。我們希望判別器判斷圖片是真的,所以`y2`設為 1。注意,為了避免警告,將`trainable`屬性設為`False`。 這樣就好了!如果展示生成出來的圖片(見圖 17-16),可以看到在第一個周期的后期,圖片看起來已經接近 Fashion MNIST 的圖片了。 ![](https://img.kancloud.cn/61/46/6146e45d7da1c3ddc4e079efe6c69bc5_1440x710.png)圖 17-16 GAN 訓練一個周期后,生成的圖片 不過,再怎么訓練,圖片的質量并沒有提升,還發現在有的周期 GAN 完全忘了學到了什么。為什么會這樣?貌似訓練 GAN 很有挑戰。接下來看看原因。 ### 訓練 GAN 的難點 在訓練中,生成器和判別器不斷試圖超越對方,這是一個零和博弈。隨著訓練的進行,可能會達成博弈學家稱為納什均衡的狀態:每個選手都不改變策略,并認為對方也不會改變策略。例如,當所有司機都靠左行駛時,就達到了納什均衡:沒有司機會選擇換邊。當然,也有第二種可能:每個人都靠右行駛。不同的初始狀態和動力學會導致不同的均衡。在這個例子中,達到均衡時,只有一種最優策略,但納什均衡包括多種競爭策略(比如,捕食者追逐獵物,獵物試圖逃跑,兩者都要改變策略)。 如何將博弈論應用到 GAN 上呢?論文作者證明,GAN 只能達到一種均衡狀態:生成器產生完美的真實圖片,同時讓判別器來判斷(50%為真,50%為假)。這是件好事:看起來只要訓練 GAN 足夠久,就會達到均衡,獲得完美的生成器。不過,并沒有這么簡單:沒有人能保證一定能達到均衡。 最大的困難是模式坍塌:生成器的輸出逐漸變得不那么豐富。為什么會這樣?假設生成器產生的鞋子圖片比其它類的圖片更讓人信服,假鞋子圖片就會更多的欺騙判別器,就會導致生成更多的鞋子圖片。逐漸的,生成器會忘掉如何生成其它類的圖片。同時,判別器唯一能看到的就是鞋子圖片,所以判別器也會忘掉如何判斷其它類的圖片。最終,當判別器想要區分假鞋和真鞋時,生成器會被迫生成其它類。生成器可能變成善于襯衫,而忘了鞋子,判別器也會發生同樣的轉變。GAN 會逐漸在一些類上循環,從而對哪一類都不擅長。 另外,因為生成器和判別器不斷試探對方,它們的參數可能不斷搖擺。訓練可能一開始正常,但因為不穩定性,會突然發散。又因為多種因素可能會影響動力學,GAN 會對超參數特別敏感:微調超參數會特別花費時間。 這些問題自從 2014 年就一直困擾著人們:人們發表了許多論文,一些論文提出新的損失函數、或穩定化訓練的手段、或避免模式坍塌。例如,經驗接力:將生成器在每個迭代產生的圖片存儲在接力緩存中(逐次丟棄舊的生成圖),使用真實圖片和從緩存中取出的圖片訓練判別器。這樣可以降低判別器對生成器的最后一個輸出過擬合的幾率。另外一個方法是小批次判別:測量批次中圖片的相似度,然后將數據傳給判別器,判別器就可以刪掉缺乏散度的假圖片。這可以鼓勵生成器產生更多類的圖片,避免模式坍塌。 總而言之,這是一個非常活躍的研究領域,GAN 的動力學仍然沒有徹底搞清。好消息是人們已經取得了一定成果,效果不俗。接下來看看一些成功的架構,從深度卷積 GAN 開始,這是幾年前的前沿成果。然后再看兩個新近的(更復雜的)架構。 ### 深度卷積 GAN 2014 年的原始 GAN 論文是用卷積層實驗的,但只用來生成小圖片。不久之后,許多人使用深度卷積網絡為大圖片創建 GAN。過程艱難,因為訓練不穩定,但最終 Alec Radford 等人試驗了許多不同的架構和超參數,在 2015 年取得了成功。他們將最終架構稱為深度卷積 GAN(DCGAN)。他們提出的搭建穩定卷積 GAN 的建議如下: * (判別器中)用卷積步長(strided convolutions)、(生成器中)用轉置卷積,替換池化層。 * 生成器和判別器都使用批歸一化,除了生成器的輸出層和判別器的輸入層。 * 去除深層架構中的全連接隱藏層。 * 生成器的輸出層使用 tanh 激活,其它層使用 ReLU 激活。 * 判別器的所有層使用 leaky ReLU 激活。 這些建議在許多任務中有效,但存在例外,所以你還是需要嘗試不同的超參數(事實上,改變隨機種子,再訓練模型,可能就成功了)。例如,下面是一個小型的 DCGAN,在 Fashion MNIST 上效果不錯: ```py codings_size = 100 generator = keras.models.Sequential([ keras.layers.Dense(7 * 7 * 128, input_shape=[codings_size]), keras.layers.Reshape([7, 7, 128]), keras.layers.BatchNormalization(), keras.layers.Conv2DTranspose(64, kernel_size=5, strides=2, padding="same", activation="selu"), keras.layers.BatchNormalization(), keras.layers.Conv2DTranspose(1, kernel_size=5, strides=2, padding="same", activation="tanh") ]) discriminator = keras.models.Sequential([ keras.layers.Conv2D(64, kernel_size=5, strides=2, padding="same", activation=keras.layers.LeakyReLU(0.2), input_shape=[28, 28, 1]), keras.layers.Dropout(0.4), keras.layers.Conv2D(128, kernel_size=5, strides=2, padding="same", activation=keras.layers.LeakyReLU(0.2)), keras.layers.Dropout(0.4), keras.layers.Flatten(), keras.layers.Dense(1, activation="sigmoid") ]) gan = keras.models.Sequential([generator, discriminator]) ``` 生成器的編碼大小為 100,將其投影到 6272 個維度上(7 * 7 * 128),將結果變形為 7 × 7 × 128 的張量。這個張量經過批歸一化,然后輸入給步長為 2 的轉置卷積層,從 7 × 7 上采樣為 14 × 14,深度從 128 降到 64。結果再做一次批歸一化,傳給另一個步長為 2 的轉置卷積層,從 14 × 14 上采樣為 28 × 28,深度從 64 降到 1。這個層使用 tanh 激活函數,輸出范圍是-1 到 1。因為這個原因,在訓練 GAN 之前,需要收縮訓練集到相同的范圍。還需要變形,加上通道維度: ```py X_train = X_train.reshape(-1, 28, 28, 1) * 2\. - 1\. # 變形和收縮 ``` 判別器看起來很像英語二元分類的常規 CNN,除了使用的不是最大池化層降采樣圖片,而是使用卷積步長。另外,使用的激活函數是 leaky ReLU。 總之,我們遵守了 DCGAN 的建議,除了將判別器中的`BatchNormalization`替換成了`Dropout`層(否則訓練會變得不穩定),生成器的 ReLU 替換為 SELU。你可以隨意調整這個架構:可以看到對超參數(特別是學習率)的敏感度。 最后,要創建數據集,然后編譯訓練模型,使用和之前一樣的代碼。經過 50 個周期的訓練,生成器的圖片見圖 17-17。還是不怎么完美,但一些圖片已經很逼真了。 ![](https://img.kancloud.cn/cf/e9/cfe9cf6f56c472852ca06416054220a5_1441x715.png)圖 17-17 DCGAN 經過 50 個周期的訓練,生成的圖片 如果擴大這個架構,然后用更大的面部數據集訓練,可以得到相當逼真的圖片。事實上,DCGAN 可以學習到許多有意義的潛在表征,見圖 17-18:從生成的諸多圖片中手動選取了九張(左上),包括三張戴眼鏡的男性,三張不戴眼鏡的男性,和三張不戴眼鏡的女性。對于每一類,對其編碼做平均,用平均的結果再生成一張圖片(放在下方)。總之,下方的圖片是上方圖片的平均。但不是簡單的像素平均,而是潛在空間的平均,所以看起來仍是正常的人臉。如果用戴眼鏡的男性,減去不戴眼鏡的男性,加上不戴眼鏡的女性,使用平均編碼,就得到了右邊 3 × 3 網格的正中的圖片,一個戴眼鏡的女性!其它八張是添加了一些噪聲的結果,用于解釋 DCGAN 的語義插值能力。可以用人臉做加減法就像科幻小說一樣! ![](https://img.kancloud.cn/25/1f/251f8e512fb8bad79a3f908956e4fc7f_1439x741.png)圖 17-18 面部的矢量運算(來自 DCGAN 論文的圖 7) > 提示:如果將圖片的類作為另一個輸入,輸入給生成器和判別器,它們都能學到每個類的樣子,你就可以控制生成器產生圖片的類。這被稱為條件 GAN(CGAN)。 但是,DCGAN 并不完美。比如,當你使用 DCGAN 生成非常大的圖片時,通常是局部逼真,但整體不協調(比如 T 恤的一個袖子比另一個長很多)。如何處理這種問題呢? ### GAN 的漸進式變大 Nvidia 研究員 Tero Karras 等人在 2018 年發表了一篇[論文](https://links.jianshu.com/go?to=https%3A%2F%2Fhoml.info%2Fprogan),提出了一個重要方法:他們建議在訓練時,先從生成小圖片開始,然后逐步給生成器和判別器添加卷積層,生成越來越大的圖片(4 × 4, 8 × 8, 16 × 16, …, 512 × 512, 1,024 × 1,024)。這個方法和棧式自編碼器的貪婪層級訓練很像。余下的層添加到生成器的末端和判別器的前端,之前訓練好的層仍然可訓練。 例如,當生成器的輸出從 4 × 4 變為 8 × 8 時(見圖 17-19),在現有的卷積層上加上一個上采樣層(使用近鄰過濾),使其輸出 8 × 8 的特征映射。再接著傳給一個新的卷積層(使用 same 填充,步長為 1,輸出為 8 × 8)。接著是一個新的輸出卷積層:這是一個常規卷積層,核大小為 1,將輸出投影到定好的顏色通道上(比如 3)。為了避免破壞第一個訓練好的卷積層的權重,最后的輸出是原始輸出層(現在的輸出是 8 × 8 的特征映射)的權重之和。新輸出的權重是α,原始輸出的權重是 1-α,α逐漸從 0 變為 1。換句話說,新的卷積層(圖 17-19 中的虛線)是淡入的,而原始輸出層淡出。向判別器(跟著平均池化層做降采樣)添加新卷積層時,也是用相似的淡入淡出方法。 ![](https://img.kancloud.cn/d0/a8/d0a810410d5af2318d98d1057f965483_1420x1019.png)圖 17-19 GAN 的漸進式變大:GAN 生成器輸出 4 × 4 的彩色圖片(左);將其擴展為 8 × 8 的圖片(右) 這篇文章還提出了一些其它的方法,用于提高輸出的散度(避免模式坍塌),使訓練更穩定: * 小批次標準差層 添加在判別器的靠近末端的位置。對于輸入的每個位置,計算批次(`S = tf.math.reduce_std(inputs, axis=[0, -1])`)中,所有通道所有實例的標準差。接著,這些標準差對所有點做平均,得到一個單值(`v = tf.reduce_?mean(S)`)。最后,給批次中的每個實例添加一個額外的特征映射,填入計算得到的單值(`tf.concat([inputs, tf.fill([batch_size, height, width, 1], v)], axis=-1)`)。這樣又什么用呢?如果生成器產生的圖片沒有什么偏差,則判別器的特征映射的標準差會特別小。有了這個層,判別器就可以做出判斷。可以讓生成器產生高散度的輸出,降低模式坍塌的風險。 * 相等的學習率 使用一個簡單的高斯分布(平均值為 0,標準差為 1)初始化權重,而不使用 He 初始化。但是,權重在運行時(即,每次執行層)會變小:會除以![\sqrt{2/ninputs}](https://img.kancloud.cn/e9/ec/e9ec1735d32942c8e2659cb7f1a9ca42_76x29.png),n<sub>inputs</sub>是層的輸入數。這篇論文說,使用這個方法可以顯著提升 GAN 使用 RMSProp、Adam 和其它適應梯度優化器時的性能。事實上,這些優化器用估計標準差(見第 11 章)歸一化了梯度更新,所以有較大動態范圍的參數需要更長時間訓練,而較小動態范圍的參數可能更新過快,會導致不穩定。通過縮放模型的部分參數,可以保證參數的動態范圍在訓練過程中一致,可以用相同的速度學習。這樣既加速了訓練,也做到了穩定。 * 像素級歸一化層 生成器的每個卷積層之后添加。它能歸一化每個激活函數,基于相同圖片相同位置的所有激活,而且跨通道(除以平均激活平方的平方根)。在 TensorFlow 的代碼中,這是`inputs / tf.sqrt(tf.reduce_mean(tf.square(X), axis=-1, keepdims=True) + 1e-8)`(平滑項 1e-8 用于避免零除)。這種方法可以避免生成器和判別器的過分競爭導致的激活爆炸。 使用所有這些方法,作者制作出了[非常逼真的人臉圖片](https://links.jianshu.com/go?to=https%3A%2F%2Fhoml.info%2Fprogandemo)。但如何給“逼真”下定義呢?GAN 的評估時一大挑戰:盡管可以自動評估生成圖片的散度,判斷質量要棘手和主觀的多。一種方法是讓人來打分,但成本高且耗時。因此作者建議比較生成圖和訓練圖的局部圖片結構,在各個層次比較。這個想法使他們創造出了另一個突破性的成果:StyleGAN。 ### StyleGAN 相同的 Nvidia 團隊在 2018 年的一篇[論文](https://links.jianshu.com/go?to=https%3A%2F%2Fhoml.info%2Fstylegan)中提出了高性能的高清圖片生成架構,StyleGAN。作者在生成器中使用了風格遷移方法,使生成的圖片和訓練圖片在每個層次,都有相同的局部結構,極大提升了圖片的質量。判別器和損失函數沒有變動,只修改了生成器。StyleGAN 包含兩個網絡(見圖 17-20): * 映射網絡 一個八層的 MLP,將潛在表征`z`(即,編碼)映射為矢量`w`。矢量然后傳給仿射變換(即,沒有激活函數的緊密層,用圖 17-20 中的框 A 表示),輸出許多矢量。這些矢量在不同級別控制著生成圖片的風格,從細粒度紋理(比如,頭發顏色)到高級特征(比如,成人或孩子)。總而言之,映射網絡將編碼變為許多風格矢量。 * 合成網絡 負責生成圖片。它有一個固定的學好的輸入(這個輸入在訓練之后是不變的,但在訓練中被反向傳播更新)。和之前一樣,合成網絡使用多個卷積核上采樣層處理輸入,但有兩處不同:首先,輸入和所有卷積層的輸出(在激活函數之前)都添加了噪音。第二,每個噪音層的后面是一個適應實例歸一化(AdaIN)層:它獨立標準化每個特征映射(減去平均值,除以標準差),然后使用風格矢量確定每個特征映射的縮放和偏移(風格矢量對每個特征映射包含一個縮放和一個偏置項)。 ![](https://img.kancloud.cn/1f/48/1f48f616e9c69db6a7e05b3ee9b9fc48_1104x1229.png)圖 17-20 StyleGAN 的生成器架構(StyleGAN 論文的圖 1 的一部分) 在編碼層獨立添加噪音非常重要。圖片的一些部分是很隨機的,比如雀斑和頭發的確切位置。在早期的 GAN 中,這個隨機性要么來自編碼,要么是生成器的一些偽噪音。如果來自編碼,意味著生成器要用編碼的很重要的一部分來存儲噪音:這樣會非常浪費。另外,噪音會在網絡中流動,直到生成器的最后一層:這是一種沒有必要的約束,會顯著減慢訓練。最后,因為噪音的存在,會出現一些視覺偽影。如果是生成器來制造偽噪音,噪音可能不夠真實,造成更多的視覺偽影。另外,用生成器的一部分權重來生成偽噪音,這也是一種浪費。通過添加額外的噪音輸入,可以避免所有這些問題;GAN 可以利用噪音,給圖片的每個部分添加隨機量。 添加的噪音在每個級別都不同。每個噪音輸入包含一個單獨的包含高斯噪音的特征映射,廣播到所有特征映射上(給定級別),然后在添加前用每個特征的縮放因子縮放(這是圖 17-20 的框 B)。 最后,StyleGAN 使用了一種稱為混合正則(或風格混合)的方法,生成圖的一定比例使用兩個編碼來生成。特別的,編碼 c<sub>1</sub> 和 c<sub>2</sub>發送給映射網絡,得到兩個風格矢量 w<sub>1</sub> 和 w<sub>2</sub>。然后合成網絡使用風格 w<sub>1</sub>生成第一級,用 w<sub>2</sub>生成其余的。級的選取是隨機的。這可以防止模型認為臨近的級是有關聯的,會導致 GAN 的局部性,每個風格矢量只會影響生成圖的有限數量的特性。 GAN 的種類如此之多,用一本書才能介紹全。希望這里的內容可以告訴你 GAN 的主要觀點,以及繼續學習的動力。如果你對數學概念掌握不好,可以看看網上的博客。然后就可以創建自己的 GAN 了,如果一開始碰到問題,千萬別氣餒:有問題是正常的,通常要好好練習,才能掌握好。如果對實現細節不明白,可以看看別人的 Keras 和 TensorFlow 實現。事實上,如果你只是想快速獲得一些經驗的結果,可以使用預訓練模型(例如,存在適用于 Keras 的 StyleGAN 預訓練模型)。 下一章會介紹深度學習的另一領域:深度強化學習。 ## 練習 1. 自編碼器主要用來做什么? 2. 假設你想訓練一個分類器,有許多未打標簽的訓練數據,只有一千多打了標簽的數據。如何使用自編碼器來解決這個問題? 3. 如果自編碼器完美重建了輸入,它一定是個好的自編碼器嗎?如何評估自編碼器的表現? 4. 自編碼器的欠完成和過完成是什么?超欠完成的風險是什么?過完成的風險是什么? 5. 如何將棧式自編碼器的權重連起來?這么做的意義是什么? 6. 什么是生成式模型?可以舉出生成式自編碼器的例子嗎? 7. GAN 是什么?可以用于什么任務? 8. 訓練 GAN 的難點是什么? 9. 用去噪音自編碼器預訓練一個圖片分類器。可以使用 MNIST,或是更復雜的圖片數據集,比如 CIFAR10。不管用的是什么數據集,遵循下面的步驟: * 將數據集分成訓練集和測試集。在完整訓練集上,訓練一個深度去噪音自編碼器。 * 檢查圖片正確重建了。可視化最激活編碼層神經元的圖片。 * 搭建一個分類 DNN,使用自編碼器的淺層。用訓練集中的 500 張圖片來訓練。然后判斷預訓練是否提升了性能? 10. 用剛才選擇的數據集,訓練一個變分自編碼器。用它來生成圖片。或者,用一個沒有標簽的數據集,來生成新樣本。 11. 訓練一個 DCGAN 來處理選擇的數據集,生成新圖片。添加經驗接力,看看它是否有作用。再將其變為一個條件 GAN,可以控制生成的類。 參考答案見附錄 A。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看