Tensorflow深度學習-自編碼器

前面我們介紹了在給出樣本及其的標籤的情況下,神經網絡如何學習的算法,這類算 法需要學習的是在給定樣本𝒙下的條件概率𝑃(𝑦|𝒙)。在社交網絡蓬勃發展的今天,獲取海量 的樣本數據𝒙,如照片、語音、文本等,是相對容易的,但困難的是獲取這些數據所對應 的標籤信息,例如機器翻譯,除了收集源語言的對話文本外,還需要待翻譯的目標語言文 本數據。數據的標註工作目前主要還是依賴人的先驗知識(Prior Knowledge)來完成,如亞馬 遜的 Mechanical Turk 系統專門負責數據標註業務,從全世界招納兼職人員完成客戶的數據 標註任務。深度學習所需要的數據規模一般非常大,這種強依賴人工完成數據標註的方式 代價較高,而且不可避免地引入標註人員的主觀先驗偏差。

面對海量的無標註數據,有沒有辦法能夠從中學習到數據的分佈𝑃(𝒙)的算法?這就是 我們這章要介紹的無監督學習(Unsupervised Learning)算法。特別地,如果算法把𝒙作爲監 督信號來學習,這類算法稱爲自監督學習(Self-supervised Learning),本章要介紹的自編碼 器算法就是屬於自監督學習範疇。

 

特徵降維(Dimensionality Reduction)在 機器學習中有廣泛的應用,比如文件壓縮(Compression)、數據預處理(Preprocessing)等。最 常見的降維算法有主成分分析法(Principal components analysis,簡稱 PCA),通過對協方差 矩陣進行特徵分解而得到數據的主要成分,但是 PCA 本質上是一種線性變換,提取特徵的 能力極爲有限。

 

那麼能不能利用神經網絡的強大非線性表達能力去學習到低維的數據表示呢?問題的 關鍵在於,訓練神經網絡一般需要一個顯式的標籤數據(或監督信號),但是無監督的數據 沒有額外的標註信息,只有數據𝒙本身。

希望神經網絡 能夠學習到映射𝑓 : 𝒙 → 𝒙。即x->z->x。

我們把𝑔𝜃1 看成一個數據編碼(Encode)的過程,把高維度的輸入𝒙編碼成低維度的隱變量𝒛(Latent Variable,或隱藏變量),稱爲 Encoder 網絡(編碼器);h𝜃2看成數據解碼(Decode)的過程,把 編碼過後的輸入𝒛解碼爲高維度的𝒙,稱爲 Decoder 網絡(解碼器)。

編碼器和解碼器共同完成了輸入數據𝒙的編碼和解碼過程,我們把整個網絡模型𝑓 叫做自動𝜃 編碼器(Auto-Encoder),簡稱自編碼器。如果使用深層神經網絡來參數化𝑔𝜃1 和h𝜃2 函數,則稱爲深度自編碼器(Deep Auto-encoder),如圖所示:

自編碼器能夠將輸入變換到隱藏向量𝒛,並通過解碼器重建(Reconstruct,或恢復)出𝒙。 我們希望解碼器的輸出能夠完美地或者近似恢復出原來的輸入,即𝒙 ≈ 𝒙,那麼,自編碼器 的優化目標可以寫成:

Minimize L = dist(x, \bar{x})

\bar{x} = h_{\theta_{2} }(g_{\theta_{1} }(x))

其中dist(𝒙, 𝒙)表示 𝒙和𝒙的距離度量,稱爲重建誤差函數。最常見的度量方法有歐氏距離 (Euclidean distance)的平方。

自編碼器網絡和普通的神經網絡並沒有本質的區別,只不 過訓練的監督信號由標籤𝒚變成了自身𝒙。藉助於深層神經網絡的非線性特徵提取能力,自 編碼器可以獲得良好的數據表示,相對於 PCA 等線性方法,自編碼器性能更加優秀,甚至 可以更加完美的恢復出輸入𝒙。

import  os
import  tensorflow as tf
import  numpy as np
from    tensorflow import keras
from    tensorflow.keras import Sequential, layers
from    PIL import Image
from    matplotlib import pyplot as plt



tf.random.set_seed(22)
np.random.seed(22)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert tf.__version__.startswith('2.')


def save_images(imgs, name):
    new_im = Image.new('L', (280, 280))

    index = 0
    for i in range(0, 280, 28):
        for j in range(0, 280, 28):
            im = imgs[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)


h_dim = 20
batchsz = 512
lr = 1e-3


(x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data()
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.
# we do not need label
train_db = tf.data.Dataset.from_tensor_slices(x_train)
train_db = train_db.shuffle(batchsz * 5).batch(batchsz)
test_db = tf.data.Dataset.from_tensor_slices(x_test)
test_db = test_db.batch(batchsz)

print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)



class AE(keras.Model):

    def __init__(self):
        super(AE, self).__init__()

        # Encoders
        self.encoder = Sequential([
            layers.Dense(256, activation=tf.nn.relu),
            layers.Dense(128, activation=tf.nn.relu),
            layers.Dense(h_dim)
        ])

        # Decoders
        self.decoder = Sequential([
            layers.Dense(128, activation=tf.nn.relu),
            layers.Dense(256, activation=tf.nn.relu),
            layers.Dense(784)
        ])


    def call(self, inputs, training=None):
        # [b, 784] => [b, 10]
        h = self.encoder(inputs)
        # [b, 10] => [b, 784]
        x_hat = self.decoder(h)

        return x_hat



model = AE()
model.build(input_shape=(None, 784))
model.summary()

optimizer = tf.optimizers.Adam(lr=lr)

for epoch in range(100):

    for step, x in enumerate(train_db):

        #[b, 28, 28] => [b, 784]
        x = tf.reshape(x, [-1, 784])

        with tf.GradientTape() as tape:
            x_rec_logits = model(x)

            rec_loss = tf.losses.binary_crossentropy(x, x_rec_logits, from_logits=True)
            rec_loss = tf.reduce_mean(rec_loss)

        grads = tape.gradient(rec_loss, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))


        if step % 100 ==0:
            print(epoch, step, float(rec_loss))


        # evaluation
        x = next(iter(test_db))
        logits = model(tf.reshape(x, [-1, 784]))
        x_hat = tf.sigmoid(logits)
        # [b, 784] => [b, 28, 28]
        x_hat = tf.reshape(x_hat, [-1, 28, 28])

        # [b, 28, 28] => [2b, 28, 28]
        x_concat = tf.concat([x, x_hat], axis=0)
        x_concat = x_hat
        x_concat = x_concat.numpy() * 255.
        x_concat = x_concat.astype(np.uint8)
        save_images(x_concat, 'ae_images/rec_epoch_%d.png'%epoch)

下面進行自編碼器變種的學習

一般而言,自編碼器網絡的訓練較爲穩定,但是由於損失函數是直接度量重建樣本與 真實樣本的底層特徵之間的距離,而不是評價重建樣本的逼真度和多樣性等抽象指標,因 此在某些任務上效果一般,如圖片重建,容易出現重建圖片邊緣模糊,逼真度相對真實圖 片仍有不小差距。爲了嘗試讓自編碼器學習到數據的真實分佈,產生了一系列的自編碼器 變種網絡。下面將介紹幾種典型的自編碼器變種模型。

1. Denoising Auto-Encoder

爲了防止神經網絡記憶住輸入數據的底層特徵,Denoising Auto-Encoders 給輸入數據 添加隨機的噪聲擾動,如給輸入𝒙添加採樣自高斯分佈的噪聲𝜀:

𝑥̃ = 𝑥 + 𝜀, 𝜀~𝒩(0, var)

添加噪聲後,網絡需要從̃𝒙學習到數據的真實隱藏變量 z,並還原出原始的輸入𝒙,如圖 所示。模型的優化目標爲:

\theta^{*} = argmin

公式有時候能打,有時候打不出來。。。就是下面這個公式,

這個dist是什麼?

2. Dropout Auto-Encoder

自編碼器網絡同樣面臨過擬合的風險,Dropout Auto-Encoder 通過隨機斷開網絡的連接 來減少網絡的表達能力,防止過擬合。Dropout Auto-Encoder 實現非常簡單,通過在網絡層中插入 Dropout 層即可實現網絡連接的隨機斷開。

3. Adversarial Auto-Encoder

爲了能夠方便地從某個已知的先驗分佈中𝑝(𝒛)採樣隱藏變量𝒛,方便利用𝑝(𝒛)來重建輸 入,對抗自編碼器(Adversarial Auto-Encoder)利用額外的判別器網絡(Discriminator,簡稱 D 網絡)來判定降維的隱藏變量𝒛是否採樣自先驗分佈𝑝(𝒛),如圖 12.10 所示。判別器網絡的 輸出爲一個屬於[0,1]區間的變量,表徵隱藏向量是否採樣自先驗分佈𝑝(𝒛):所有采樣自先 驗分佈𝑝(𝒛)的𝒛標註爲真,採樣自編碼器的條件概率𝑞(𝒛|𝒙)的𝒛標註爲假。通過這種方式訓 練,除了可以重建樣本,還可以約束條件概率分佈𝑞(𝒛|𝒙)逼近先驗分佈𝑝(𝒛)。

這個下面那個分佈結果是怎麼來的,我有些不太理解。

4. 變分自編碼器

基本的自編碼器本質上是學習輸入𝒙和隱藏變量𝒛之間映射關係,它是一個判別模型 (Discriminative model),並不是生成模型(Generative model)。

爲什麼這個說呢?

個人猜想是還需要損失函數對比。

給定隱藏變量的分佈P(𝒛),如果可以學習到條件概率分佈P(𝒙|𝒛),則通過對聯合概率 分佈P(𝒙, 𝒛) = P(𝒙|𝒛)P(𝒛)進行採樣,生成不同的樣本。變分自編碼器(Variational Auto- Encoders,簡稱 VAE)就可以實現此目的。

從神經網絡的角度來看,VAE 相對於自編碼器模型,同樣具有編碼器和解碼器兩個子 網絡。解碼器接受輸入𝒙,輸出爲隱變量𝒛;解碼器負責將隱變量𝒛解碼爲重建的𝒙。不同的 是,VAE 模型對隱變量𝒛的分佈有顯式地約束,希望隱變量𝒛符合預設的先驗分佈P(𝒛)。因 此,在損失函數的設計上,除了原有的重建誤差項外,還添加了隱變量𝒛分佈的約束項。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章