原文地址:https://blog.csdn.net/marsjhao/article/details/73480859
一、什么是自編碼器(Autoencoder)
自動編碼器是一種數據的壓縮算法,其中數據的壓縮和解壓縮函數是數據相關的、有損的、從樣本中自動學習的。在大部分提到自動編碼器的場合,壓縮和解壓縮的函數是通過神經網絡實現的。
1)自動編碼器是數據相關的(data-specific 或 data-dependent),這意味着自動編碼器只能壓縮那些與訓練數據類似的數據。比如,使用人臉訓練出來的自動編碼器在壓縮別的圖片,比如樹木時性能很差,因為它學習到的特征是與人臉相關的。
2)自動編碼器是有損的,意思是解壓縮的輸出與原來的輸入相比是退化的,MP3,JPEG等壓縮算法也是如此。這與無損壓縮算法不同。
3)自動編碼器是從數據樣本中自動學習的,這意味着很容易對指定類的輸入訓練出一種特定的編碼器,而不需要完成任何新工作。
搭建一個自動編碼器需要完成下面三樣工作:搭建編碼器,搭建解碼器,設定一個損失函數,用以衡量由於壓縮而損失掉的信息。編碼器和解碼器一般都是參數化的方程,並關於損失函數可導,典型情況是使用神經網絡。編碼器和解碼器的參數可以通過最小化損失函數而優化,例如SGD。
自編碼器是一個自監督的算法,並不是一個無監督算法。自監督學習是監督學習的一個實例,其標簽產生自輸入數據。要獲得一個自監督的模型,你需要一個靠譜的目標跟一個損失函數,僅僅把目標設定為重構輸入可能不是正確的選項。基本上,要求模型在像素級上精確重構輸入不是機器學習的興趣所在,學習到高級的抽象特征才是。事實上,當主要任務是分類、定位之類的任務時,那些對這類任務而言的最好的特征基本上都是重構輸入時的最差的那種特征。
目前自編碼器的應用主要有兩個方面,第一是數據去噪,第二是為進行可視化而降維。配合適當的維度和稀疏約束,自編碼器可以學習到比PCA等技術更有意思的數據投影。
對於2D的數據可視化,t-SNE(讀作tee-snee)或許是目前最好的算法,但通常還是需要原數據的維度相對低一些。所以,可視化高維數據的一個好辦法是首先使用自編碼器將維度降低到較低的水平(如32維),然后再使用t-SNE將其投影在2D平面上。
二、幾種自編碼器
自編碼器(autoencoder)是神經網絡的一種,經過訓練后能嘗試將輸入復制到輸出。自編碼器()autoencoder)內部有一個隱藏層 h,可以產生編碼(code)表示輸入。該網絡可以看作由兩部分組成:一個由函數 h = f(x) 表示的編碼器和一個生成重構的解碼器 r = g(h)。如果一個自編碼器只是簡單地學會將處處設置為 g(f(x)) = x,那么這個自編碼器就沒什么特別的用處。相反,我們不應該將自編碼器設計成輸入到輸出完全相等。這通常需要向自編碼器強加一些約束,使它只能近似地復制,並只能復制與訓練數據相似的輸入。這些約束強制模型考慮輸入數據的哪些部分需要被優先復制,因此它往往能學習到數據的有用特性。
1. 欠完備自編碼器
從自編碼器獲得有用特征的一種方法是限制 h的維度比 x 小,這種編碼維度小於輸入維度的自編碼器稱為欠完備(undercomplete)自編碼器。學習欠完備的表示將強制自編碼器捕捉訓練數據中最顯著的特征。
學習過程可以簡單地描述為最小化一個損失函數L(x,g(f(x))),其中 L 是一個損失函數,懲罰g(f(x)) 與 x 的差異,如均方誤差。當解碼器是線性的且 L 是均方誤差,欠完備的自編碼器會學習出與 PCA 相同的生成子空間。這種情況下,自編碼器在訓練來執行復制任務的同時學到了訓據的主元子空間。如果編碼器和解碼器被賦予過大的容量,自編碼器會執行復制任務而捕捉不到任何有關數據分布的有用信息。
2. 正則自編碼器
正則自編碼器使用的損失函數可以鼓勵模型學習其他特性(除了將輸入復制到輸出),而不必限制使用淺層的編碼器和解碼器以及小的編碼維數來限制模型的容量。這些特性包括稀疏表示、表示的小導數、以及對噪聲或輸入缺失的魯棒性。即使模型容量大到足以學習一個無意義的恆等函數,非線性且過完備的正則自編碼器仍然能夠從數據中學到一些關於數據分布的有用信息。
2.1 稀疏自編碼器
稀疏自編碼器簡單地在訓練時結合編碼層的稀疏懲罰 Ω(h) 和重構誤差:L(x,g(f(x))) + Ω(h),其中 g(h) 是解碼器的輸出,通常 h 是編碼器的輸出,即 h = f(x)。稀疏自編碼器一般用來學習特征,以便用於像分類這樣的任務。稀疏正則化的自編碼器必須反映訓練數據集的獨特統計特征,而不是簡單地充當恆等函數。以這種方式訓練,執行附帶稀疏懲罰的復制任務可以得到能學習有用特征的模型。
2.2 去噪自編碼器
去噪自編碼器(denoisingautoencoder, DAE)最小化L(x,g(f(˜ x))),其中 ˜ x 是被某種噪聲損壞的 x 的副本。因此去噪自編碼器必須撤消這些損壞,而不是簡單地復制輸入。
2.3 收縮自編碼器
另一正則化自編碼器的策略是使用一個類似稀疏自編碼器中的懲罰項 Ω,
1)自動編碼器是數據相關的(data-specific 或 data-dependent),這意味着自動編碼器只能壓縮那些與訓練數據類似的數據。比如,使用人臉訓練出來的自動編碼器在壓縮別的圖片,比如樹木時性能很差,因為它學習到的特征是與人臉相關的。
2)自動編碼器是有損的,意思是解壓縮的輸出與原來的輸入相比是退化的,MP3,JPEG等壓縮算法也是如此。這與無損壓縮算法不同。
3)自動編碼器是從數據樣本中自動學習的,這意味着很容易對指定類的輸入訓練出一種特定的編碼器,而不需要完成任何新工作。
搭建一個自動編碼器需要完成下面三樣工作:搭建編碼器,搭建解碼器,設定一個損失函數,用以衡量由於壓縮而損失掉的信息。編碼器和解碼器一般都是參數化的方程,並關於損失函數可導,典型情況是使用神經網絡。編碼器和解碼器的參數可以通過最小化損失函數而優化,例如SGD。
自編碼器是一個自監督的算法,並不是一個無監督算法。自監督學習是監督學習的一個實例,其標簽產生自輸入數據。要獲得一個自監督的模型,你需要一個靠譜的目標跟一個損失函數,僅僅把目標設定為重構輸入可能不是正確的選項。基本上,要求模型在像素級上精確重構輸入不是機器學習的興趣所在,學習到高級的抽象特征才是。事實上,當主要任務是分類、定位之類的任務時,那些對這類任務而言的最好的特征基本上都是重構輸入時的最差的那種特征。
目前自編碼器的應用主要有兩個方面,第一是數據去噪,第二是為進行可視化而降維。配合適當的維度和稀疏約束,自編碼器可以學習到比PCA等技術更有意思的數據投影。
對於2D的數據可視化,t-SNE(讀作tee-snee)或許是目前最好的算法,但通常還是需要原數據的維度相對低一些。所以,可視化高維數據的一個好辦法是首先使用自編碼器將維度降低到較低的水平(如32維),然后再使用t-SNE將其投影在2D平面上。
二、幾種自編碼器
自編碼器(autoencoder)是神經網絡的一種,經過訓練后能嘗試將輸入復制到輸出。自編碼器()autoencoder)內部有一個隱藏層 h,可以產生編碼(code)表示輸入。該網絡可以看作由兩部分組成:一個由函數 h = f(x) 表示的編碼器和一個生成重構的解碼器 r = g(h)。如果一個自編碼器只是簡單地學會將處處設置為 g(f(x)) = x,那么這個自編碼器就沒什么特別的用處。相反,我們不應該將自編碼器設計成輸入到輸出完全相等。這通常需要向自編碼器強加一些約束,使它只能近似地復制,並只能復制與訓練數據相似的輸入。這些約束強制模型考慮輸入數據的哪些部分需要被優先復制,因此它往往能學習到數據的有用特性。
1. 欠完備自編碼器
從自編碼器獲得有用特征的一種方法是限制 h的維度比 x 小,這種編碼維度小於輸入維度的自編碼器稱為欠完備(undercomplete)自編碼器。學習欠完備的表示將強制自編碼器捕捉訓練數據中最顯著的特征。
學習過程可以簡單地描述為最小化一個損失函數L(x,g(f(x))),其中 L 是一個損失函數,懲罰g(f(x)) 與 x 的差異,如均方誤差。當解碼器是線性的且 L 是均方誤差,欠完備的自編碼器會學習出與 PCA 相同的生成子空間。這種情況下,自編碼器在訓練來執行復制任務的同時學到了訓據的主元子空間。如果編碼器和解碼器被賦予過大的容量,自編碼器會執行復制任務而捕捉不到任何有關數據分布的有用信息。
2. 正則自編碼器
正則自編碼器使用的損失函數可以鼓勵模型學習其他特性(除了將輸入復制到輸出),而不必限制使用淺層的編碼器和解碼器以及小的編碼維數來限制模型的容量。這些特性包括稀疏表示、表示的小導數、以及對噪聲或輸入缺失的魯棒性。即使模型容量大到足以學習一個無意義的恆等函數,非線性且過完備的正則自編碼器仍然能夠從數據中學到一些關於數據分布的有用信息。
2.1 稀疏自編碼器
稀疏自編碼器簡單地在訓練時結合編碼層的稀疏懲罰 Ω(h) 和重構誤差:L(x,g(f(x))) + Ω(h),其中 g(h) 是解碼器的輸出,通常 h 是編碼器的輸出,即 h = f(x)。稀疏自編碼器一般用來學習特征,以便用於像分類這樣的任務。稀疏正則化的自編碼器必須反映訓練數據集的獨特統計特征,而不是簡單地充當恆等函數。以這種方式訓練,執行附帶稀疏懲罰的復制任務可以得到能學習有用特征的模型。
2.2 去噪自編碼器
去噪自編碼器(denoisingautoencoder, DAE)最小化L(x,g(f(˜ x))),其中 ˜ x 是被某種噪聲損壞的 x 的副本。因此去噪自編碼器必須撤消這些損壞,而不是簡單地復制輸入。
2.3 收縮自編碼器
另一正則化自編碼器的策略是使用一個類似稀疏自編碼器中的懲罰項 Ω,
這迫使模型學習一個在 x 變化小時目標也沒有太大變化的函數。因為這個懲罰只對訓練數據適用,它迫使自編碼器學習可以反映訓練數據分布信息的特征。這樣正則化的自編碼器被稱為收縮自編碼器(contractive autoencoder, CAE)。這種方法與去噪自編碼器、流形學習和概率模型存在一定理論聯系。
3. 表示能力、層的大小和深度
萬能近似定理保證至少有一層隱藏層且隱藏單元足夠多的前饋神經網絡能以任意精度近似任意函數(在很大范圍里),這是非平凡深度(至少有一層隱藏層)的一個主要優點。這意味着具有單隱藏層的自編碼器在數據域內能表示任意近似數據的恆等函數。但是,從輸入到編碼的映射是淺層的。這意味這我們不能任意添加約束,比如約束編碼稀疏。深度自編碼器(編碼器至少包含一層額外隱藏層)在給定足夠多的隱藏單元的情況下,能以任意精度近似任何從輸入到編碼的映射。
深度可以指數地降低表示某些函數的計算成本。深度也能指數地減少學習一些函數所需的訓練數據量。實驗中,深度自編碼器能比相應的淺層或線性自編碼器產生更好的壓縮效率。
訓練深度自編碼器的普遍策略是訓練一堆淺層的自編碼器來貪心地預訓練相應的深度架構。所以即使最終目標是訓練深度自編碼器,我們也經常會遇到淺層自編碼器。
4. 去噪自編碼器
去噪自編碼器(denoisingautoencoder, DAE)是一類接受損壞數據作為輸入,並訓練來預測原始未被損壞數據作為輸出的自編碼器。
DAE 的訓練准則(條件高斯p(x | h))能讓自編碼器學到能估計數據分布得分的向量場 (g(f(x)) − x) ,這是 DAE 的一個重要特性。
3. 表示能力、層的大小和深度
萬能近似定理保證至少有一層隱藏層且隱藏單元足夠多的前饋神經網絡能以任意精度近似任意函數(在很大范圍里),這是非平凡深度(至少有一層隱藏層)的一個主要優點。這意味着具有單隱藏層的自編碼器在數據域內能表示任意近似數據的恆等函數。但是,從輸入到編碼的映射是淺層的。這意味這我們不能任意添加約束,比如約束編碼稀疏。深度自編碼器(編碼器至少包含一層額外隱藏層)在給定足夠多的隱藏單元的情況下,能以任意精度近似任何從輸入到編碼的映射。
深度可以指數地降低表示某些函數的計算成本。深度也能指數地減少學習一些函數所需的訓練數據量。實驗中,深度自編碼器能比相應的淺層或線性自編碼器產生更好的壓縮效率。
訓練深度自編碼器的普遍策略是訓練一堆淺層的自編碼器來貪心地預訓練相應的深度架構。所以即使最終目標是訓練深度自編碼器,我們也經常會遇到淺層自編碼器。
4. 去噪自編碼器
去噪自編碼器(denoisingautoencoder, DAE)是一類接受損壞數據作為輸入,並訓練來預測原始未被損壞數據作為輸出的自編碼器。
DAE 的訓練准則(條件高斯p(x | h))能讓自編碼器學到能估計數據分布得分的向量場 (g(f(x)) − x) ,這是 DAE 的一個重要特性。
5. 收縮自編碼器
收縮自編碼器 (Rifai et al.,2011a,b) 在編碼 h = f(x) 的基礎上添加了顯式的正則項,鼓勵 f 的導數盡可能小:
收縮自編碼器 (Rifai et al.,2011a,b) 在編碼 h = f(x) 的基礎上添加了顯式的正則項,鼓勵 f 的導數盡可能小:
懲罰項 Ω(h) 為平方 Frobenius范數(元素平方之和),作用於與編碼器的函數相關偏導數的 Jacobian 矩陣。
收縮(contractive)源於 CAE 彎曲空間的方式。具體來說,由於 CAE 訓練為抵抗輸入擾動,鼓勵將輸入點鄰域映射到輸出點處更小的鄰域。我們能認為這是將輸入的鄰域收縮到更小的輸出鄰域。
三、使用Keras建立簡單的自編碼器
1. 單隱含層自編碼器
建立一個全連接的編碼器和解碼器。也可以單獨使用編碼器和解碼器,在此使用Keras的函數式模型API即Model可以靈活地構建自編碼器。
50個epoch后,看起來我們的自編碼器優化的不錯了,損失val_loss: 0.1037。
收縮(contractive)源於 CAE 彎曲空間的方式。具體來說,由於 CAE 訓練為抵抗輸入擾動,鼓勵將輸入點鄰域映射到輸出點處更小的鄰域。我們能認為這是將輸入的鄰域收縮到更小的輸出鄰域。
三、使用Keras建立簡單的自編碼器
1. 單隱含層自編碼器
建立一個全連接的編碼器和解碼器。也可以單獨使用編碼器和解碼器,在此使用Keras的函數式模型API即Model可以靈活地構建自編碼器。
50個epoch后,看起來我們的自編碼器優化的不錯了,損失val_loss: 0.1037。
1 from keras.layers import Input, Convolution2D, MaxPooling2D, UpSampling2D 2 3 from keras.models import Model 4 5 from keras.datasets import mnist 6 7 import numpy as np 8 9 import matplotlib.pyplot as plt 10 11 from keras.callbacks import TensorBoard 12 13 14 15 (x_train, _), (x_test, _) = mnist.load_data() 16 17 x_train = x_train.astype('float32') / 255. 18 19 x_test = x_test.astype('float32') / 255. 20 21 x_train = np.reshape(x_train, (len(x_train), 28, 28, 1)) 22 23 x_test = np.reshape(x_test, (len(x_test), 28, 28, 1)) 24 25 noise_factor = 0.5 26 27 x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape) 28 29 x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape) 30 31 x_train_noisy = np.clip(x_train_noisy, 0., 1.) 32 33 x_test_noisy = np.clip(x_test_noisy, 0., 1.) 34 35 print(x_train.shape) 36 37 print(x_test.shape) 38 39 40 41 input_img = Input(shape=(28, 28, 1)) 42 43 44 45 x = Convolution2D(32, (3, 3), activation='relu', padding='same')(input_img) 46 47 x = MaxPooling2D((2, 2), padding='same')(x) 48 49 x = Convolution2D(32, (3, 3), activation='relu', padding='same')(x) 50 51 encoded = MaxPooling2D((2, 2), padding='same')(x) 52 53 54 55 x = Convolution2D(32, (3, 3), activation='relu', padding='same')(encoded) 56 57 x = UpSampling2D((2, 2))(x) 58 59 x = Convolution2D(32, (3, 3), activation='relu', padding='same')(x) 60 61 x = UpSampling2D((2, 2))(x) 62 63 decoded = Convolution2D(1, (3, 3), activation='sigmoid', padding='same')(x) 64 65 66 67 autoencoder = Model(inputs=input_img, outputs=decoded) 68 69 autoencoder.compile(optimizer='adadelta', loss='binary_crossentropy') 70 71 72 73 # 打開一個終端並啟動TensorBoard,終端中輸入 tensorboard --logdir=/autoencoder 74 75 autoencoder.fit(x_train_noisy, x_train, epochs=10, batch_size=256, 76 77 shuffle=True, validation_data=(x_test_noisy, x_test), 78 79 callbacks=[TensorBoard(log_dir='autoencoder', write_graph=False)]) 80 81 82 83 decoded_imgs = autoencoder.predict(x_test_noisy) 84 85 86 87 n = 10 88 89 plt.figure(figsize=(30, 6)) 90 91 for i in range(n): 92 93 ax = plt.subplot(3, n, i + 1) 94 95 plt.imshow(x_test[i].reshape(28, 28)) 96 97 plt.gray() 98 99 ax.get_xaxis().set_visible(False) 100 101 ax.get_yaxis().set_visible(False) 102 103 104 105 ax = plt.subplot(3, n, i + 1 + n) 106 107 plt.imshow(x_test_noisy[i].reshape(28, 28)) 108 109 plt.gray() 110 111 ax.get_xaxis().set_visible(False) 112 113 ax.get_yaxis().set_visible(False) 114 115 116 117 ax = plt.subplot(3, n, i + 1 + 2*n) 118 119 plt.imshow(decoded_imgs[i].reshape(28, 28)) 120 121 plt.gray() 122 123 ax.get_xaxis().set_visible(False) 124 125 ax.get_yaxis().set_visible(False) 126 127 plt.show()