〖TensorFlow2.0筆記18〗自編碼器Auto-Encoders以及實戰!

自編碼器Auto-Encoders以及實戰!

一. 自編碼器Auto-Encoders

1.1. 回顧之前的知識

1.2. 無監督學習

  • 機器學習的三大方向:
  • 爲什麼需要無監督學習?
  • 通過上面瞭解了爲什麼需要無監督學習,下面看一下無監督學習的一種形態!注意:其實無監督並不是沒有目標,它也是有目標的,比如看下面的auto-encode,它的目標是什麼?它的目標就是它自己,這麼一想其實unsupervised learning也是非常有道理的。
  • 比如下面MNIST數據集,輸入這樣的圖片,經過這樣一個神經網絡NN(encoder),得到一個輸出,再經過一個網絡(decoder);也可以如下圖中的理解。這就是auto-encoder,特別簡單。
  • 可視化可以參考這個網站,比較直觀的感受784-3784維降低到3維):降維度可視化網站鏈接

1.3. Auto-Encoders中loss function如何設計

  • 介紹如下

1.4. PCA和Auto-encoder對比

  • 效果對比:
  • 再看一個可視化的效果

二. Auto-Encoders的變種

2.1. 變種1: 原圖像中引入噪聲

  • 下面講解一下auto-encoder的變種吧!首先比較簡答的是:容易想到如果只是在像素級別裏重建,這樣沒有發現一些更深層次的東西,那你很有可能只是記住一些特徵,比如這個數字,你可能直接把這個像素給記住了;那麼爲了防止記住怎麼辦?我們給原圖像加一些噪聲

2.2. 變種2: 藉助Drop out

  • Drop outHintton大神發明的,Drop out也是現在一種比較常用的技術,但是他並沒有發表在paper上面,據說是Hintton在給他的學生上課的時候隨口提出來的小trick(技巧),幫助你提升神經網絡性能的技巧。
  • 看下圖:首先在Training的時候回有目的的,讓這部分的連接斷開,具體的操作我們手動的讓這部分的權值暫時設置爲0,就是相當於暫時斷掉了。這樣可以理解爲:給你的信息有限,這樣就迫使你的系統更加的robust,你就不會依賴於所有的神經元,而是部分的神經元。然後你再Test的時候:就會讓你的這個任務變得更加的簡單了,所有的Drop out設置都恢復了。

2.3. 預備知識相對熵

  • 下面的資料來自哈工大劉遠超老師的PPT資料,表示感謝!同時參考知乎大神https://zhuanlan.zhihu.com/p/26486223

  • 信息量: 是對信息的度量,就跟時間的度量是秒一樣,當我們考慮一個離散的隨機變量x的時候,當我們觀察到的這個變量的一個具體值的時候,我們接收到了多少信息呢?多少信息用信息量來衡量,我們接受到的信息量跟具體發生的事件有關。

  • 信息的大小跟隨機事件的概率有關。越小概率的事情發生了產生的信息量越大,如湖南產生的地震了;越大概率的事情發生了產生的信息量越小,如太陽從東邊升起來了(肯定發生嘛,沒什麼信息量)。這很好理解!

  • 例子: 腦補一下我們日常的對話:師兄走過來跟我說,立波啊,今天你們湖南發生大地震了。我:啊,不可能吧,這麼重量級的新聞!湖南多低的概率發生地震啊!師兄,你告訴我的這件事,信息量巨大,我馬上打電話問問父母什麼情況。又來了一個師妹:立波師兄,我發現了一個重要情報額,原來德川師兄有女朋友額 德川比師妹早進一年實驗室,全實驗室同學都知道了這件事。我大笑一聲:哈哈哈哈,這件事大家都知道了,一點含金量都沒有,下次八卦一些其它有價值的新聞吧!orz,逃~ 因此一個具體事件的信息量應該是隨着其發生概率而遞減的,且不能爲負。

  • 但是這個表示信息量函數的形式怎麼找呢? 隨着概率增大而減少的函數形式太多了!不要着急,我們還有下面這條性質
    如果我們有倆個不相關的事件 xxyy,那麼我們觀察到的倆個事件同時發生時獲得的信息應該等於觀察到的事件各自發生時獲得的信息之和,即:
    h(x,y)=h(x)+h(y)h(x, y)=h(x)+h(y)

  • 由於 xyx,y 是倆個不相關的事件,那麼滿足 p(x,y)=p(x)p(y)p(x, y)=p(x)*p(y).

  • 根據上面推導,我們很容易看出 h(x)h(x) 一定與 p(x)p(x) 的對數有關(因爲只有對數形式的真數相乘之後,能夠對應對數的相加形式,可以試試)。因此我們有信息量公式如下:h(x)=log2p(x)h(x)=-\log _{2} p(x)

  • 下面解決倆個疑問? 1. 爲什麼有一個負號:其中負號是爲了確保信息一定是正數或者是0,總不能爲負數吧!2. 這是因爲,我們只需要信息量滿足低概率事件 xx 對應於高的信息量。那麼對數的選擇是任意的。我們只是遵循信息論的普遍傳統,使用2作爲對數的底。

  • 信息論中的熵的概念;

2.3.1. 信息熵

2.3.2. 交叉熵

2.3.3. KL散度又稱相對熵

  • 取值範圍:[0,+][0, +\infty ],當兩個分佈接近相同的時候,取值爲0;當兩個分佈差異越來越大的時候KL散度值就會越來越大。
  • 其中:DKL(PQ)=xXP(x)log1Q(x)xXP(x)log1P(x)=xXP(x)logQ(x)+xXP(x)logP(x)=xXP(x)logP(x)Q(x)\begin{aligned} \mathrm D_{K L}(\mathrm P \| \mathrm Q) &=\sum_{x \in \mathrm X} \mathrm P(x) \log \frac{1}{\mathrm Q(x)}-\sum_{x \in \mathrm X}\mathrm P(x) \log \frac{1}{\mathrm P(x)}\\ &={- \sum_{x \in \mathrm X} \mathrm P(x) \log {\mathrm Q(x)}+\sum_{x \in \mathrm X}\mathrm P(x) \log {\mathrm P(x)}} \\ &=\sum_{x \in \mathrm X}\mathrm P(x) \log \frac{ \mathrm P(x)}{\mathrm Q(x)} \\ \end{aligned}

2.3.4. 相對熵具體例子

2.3.5. 相對熵的性質

2.3.6. JS散度

2.4. 變種3:Adversarial Auto-Encoders(比較有名)

  • 這個是比較有名的,這個跟後面要介紹的GAN非常相關了,這個直接把GAN中的技術應用到這裏Auto-Encoders了,
  • 但是實際上最原始的auto-encoders大家發現;比如你從 [1,1][-1,1]變換 h 分佈的時候,重建出來的圖片它可能是一樣的,就是沒有呈現出這種分佈過來,這樣的話就比價煩人了,那麼怎麼解決這個問題呢?Adversarial Auto-Encoders 是怎樣解決這個問題的呢?如下圖添加了一個 Discriminator,還沒有講到 GAN,現在可以理解爲一個識別器(鑑別器)
  • 我們假設一個分佈,高斯分佈,均值爲 00,方差爲 11,我們希望你屬於一個這樣的分佈,如果你沒有輸入這個分佈,我們鑑別你,我們把真實的 zz 和希望得到的 zz' 都送給這個網絡做鑑別這個差距,如果差距大,就是不屬於預設的這個分佈,我們輸出一個fake,我們這個分佈屬於預設的分佈,就是屬於這個高斯分佈,我們輸出這個 11。通過這樣的話就能迫使就能是我們中間得到的這個hidden vector 除了能完成重建工作之外,還能符合我們想要的分佈,這樣就很好解決上一張圖片中所說的不平衡的問題(分佈不均勻,亂七八糟)。
  • Adversarial Auto-Encoder 額外添加了一個 Discriminator (可以理解爲一個識別器)
  • 由這個Adversarial Auto-Encoders引出這節課的重點所在。
  • sample() 是不可微的,不能進行反向傳播;
  • 之前介紹了Adversarial Auto-Encoders,我們現在從神經網絡的角度理解一下。

2.5. 對比Auto-Encoder和Adversarial Auto-Encoder的區別

  • VAE 重建的效果好一些。
  • VAE除了重建的效果比AE好一些,更重要的是作爲一個stochasticstochastic模型,它學習的是q(h)q(h),通過從q(h)q(h)samplesample出不同的hh,這樣就可以重建或者說用來做生成的模型,如果h是二維的[h0,h1][h0, h1]h0h_{0}屬於h0N(μ0,σ02)h_{0} \sim N\left(\mu_{0}, \sigma_{0}^{2}\right)的分佈, h1h_{1}屬於h1N(μ0,σ12)h_{1} \sim N\left(\mu_{0}, \sigma_{1}^{2}\right)的分佈,知道了這2個分佈,我們就改變 μ0,σ0,μ1,σ1\mu_{0},\sigma_{0}, \mu_{1},\sigma_{1},我們再samplesample一下,samplesample出來新的hh, hh再送入後面的decoderdecoder,就能得到 x\overline{x},通過多個samplesample之後得到不同的hh,也可以得到不同的x\overline{x},這樣的話就可以實現生成。
  • 看下圖橫座標h0h_{0}改變,縱座標h1h_{1}改變,改變以後相當於上面說的分佈改變了,那麼sample生成的圖片就有隨機性在裏面。

三. 基本的Auto-Encoders實戰

3.1. 訓練部分代碼實現

  • python 代碼如下:
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from PIL import Image
from matplotlib import pyplot as plt
from tensorflow.keras import Sequential, layers


tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多張image保存達到一張image裏面去。
def save_images(img, 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 = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定義超參數
h_dim = 20         # 把原來的784維護降低到20維度;
batchsz = 512      # fashion_mnist
lr = 1e-4

# 數據集加載
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train, x_test = x_train.astype(np.float32) / 255., x_test.astype(np.float32) / 255.

# we do not need label auto-encoder大家可以理解爲無監督學習,標籤其實就是本身,和自己對比;
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):
    # 1. 初始化部分
    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)

        ])

    # 2. 前向傳播的過程
    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))      # 注意這裏面用小括號還是中括號是有講究的。建議使用tuple
model.summary()

optimizer = keras.optimizers.Adam(lr=lr)
# optimizer = tf.optimizers.Adam() 都可以;

for epoch in range(1000):

    for step, x in enumerate(train_db):

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

        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.losses.MSE(x, x_rec_logits)
            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: %3d, step:%4d, loss:%9f' %(epoch, step, float(rec_loss)))

  • 測試結果如下:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/autoencoder.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
Model: "ae"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sequential (Sequential)      multiple                  236436    
_________________________________________________________________
sequential_1 (Sequential)    multiple                  237200    
=================================================================
Total params: 473,636
Trainable params: 473,636
Non-trainable params: 0
_________________________________________________________________
epoch:   0, step:   0, loss: 0.692311
epoch:   0, step: 100, loss: 0.276746
epoch:   1, step:   0, loss: 0.274721
epoch:   1, step: 100, loss: 0.250346
epoch:   2, step:   0, loss: 0.249346
epoch:   2, step: 100, loss: 0.219126
epoch:   3, step:   0, loss: 0.212125
epoch:   3, step: 100, loss: 0.192399
epoch:   4, step:   0, loss: 0.191749
epoch:   4, step: 100, loss: 0.177625
epoch:   5, step:   0, loss: 0.176804
epoch:   5, step: 100, loss: 0.169778
epoch:   6, step:   0, loss: 0.167599
epoch:   6, step: 100, loss: 0.159327

3.2. 測試部分代碼實現

  • 那麼如何進行測試呢?對於 auto-encoder 或者說 unsupervise learning;測試的時候我們只需要 test 或者叫做 evaluation
        if step % 100 ==0:
            print('epoch: %3d, step:%4d, loss:%9f' %(epoch, step, float(rec_loss)))
		##################添加的代碼##################
        # evaluation
        x = next(iter(test_db))                   # 我們從test_db中取出一張圖片;
        x_shape = tf.reshape(x, [-1, 784]).numpy()
        logits = model(x_shape)                         # 經過auto-encoder重建的效果。
        x_hat= tf.sigmoid(logits)                 # 變化到0-1之間

        # [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.        # 把數據numpy取出來,變成0-255區間;
        x_concat = x_concat.astype(np.uint8)      # 還原成numpy保存圖片的格式;
        save_images(x_concat, 'ae_image/rec_epoch_%d.png' %epoch) # 每個epoch保存一次。

3.3. 重建結果對比

  • 重建得到的圖片和原始圖片進行對比:
原始圖片 重建圖片

注意: 我們發現重建的圖片相對原始圖片存在模糊的現象,棱角美譽原圖像那麼銳利。這是因爲 auto-encoder 求出來的 loss 是根據 distance (它會追求你的整體的 loss 變的更小,所以會存在模糊掉,所以圖片的保真度不是特別好,這其實也是 GAN 存在的現象!)

四. Adversarial Auto-Encoders實戰

4.1. 訓練部分代碼實現

4.1.1. 構造模型

# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders編碼, 網絡
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小網路1:均值(均值和方差是一一對應的,所以維度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小網路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解碼,網絡
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder傳播的過程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var       	  # 注意這裏看成取了一個log函數;

    # decoder傳播的過程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        var = tf.exp(log_var)         # 去掉log, 得到方差;
        std = var**0.5                # 開根號,得到標準差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 進行還原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向傳播不同之外,還有一個額外的約束;這個約束放在損失函數中;
        # 這個約束使得你的mu, var更接近正太分佈;所以我們把mu, log_var返回;
        return x_hat, mu, log_var

注意:Variational auto-encoder 除了前向傳播不同之外,還有一個額外的約束;這個約束放在損失函數中;這個約束使得你的 mu, var 更接近正太分佈;所以我們把 mu, log_var 返回;

4.1.2. 注意損失函數部分

  • 這裏主要是加入了 KL 散度的計算(這裏都是以 ee 爲底): Let p(x)=N(μ1,σ1) and q(x)=N(μ2,σ2)\text { Let } p(x)=N\left(\mu_{1}, \sigma_{1}\right) \text { and } q(x)=N\left(\mu_{2}, \sigma_{2}\right),可以得到:
    KL(p,q)=p(x)logq(x)dx+p(x)logp(x)dx=12log(2πσ22)+σ12+(μ1μ2)22σ2212(1+log2πσ12)=logσ2σ1+σ12+(μ1μ2)22σ2212\begin{aligned} K L(p, q) &=-\int p(x) \log q(x) d x+\int p(x) \log p(x) d x \\ &=\frac{1}{2} \log \left(2 \pi \sigma_{2}^{2}\right)+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}}-\frac{1}{2}\left(1+\log 2 \pi \sigma_{1}^{2}\right) \\ &=\log \frac{\sigma_{2}}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}} -\frac{1}{2}\end{aligned}

  • 在我們這裏 p(x)=N(μ1,var)p(x)=N\left(\mu_{1}, \sqrt{\operatorname{var}}\right)q(x)=N(μ2=0,σ2=1)q(x)=N\left(\mu_{2}=0, \sigma_{2}=1\right) 代入可得:
    KL(p,q)=logσ2σ1+σ12+(μ1μ2)22σ2212=log1σ1+σ12+μ12212=logσ1+σ12+μ12212=12(2logσ1(σ12+μ12)+1)=12(logσ12(σ12+μ12)+1)\begin{aligned} K L(p, q) &=\log \frac{\sigma_{2}}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\left(\mu_{1}-\mu_{2}\right)^{2}}{2 \sigma_{2}^{2}}-\frac{1}{2}\\ &=\log \frac{1}{\sigma_{1}}+\frac{\sigma_{1}^{2}+\mu_{1}^{2}}{2 } -\frac{1}{2} \\ &= -\log \sigma_{1}+\frac{\sigma_{1}^{2}+\mu_{1}^{2}}{2 } -\frac{1}{2}\\ &= -\frac{1}{2}\left( 2 \log \sigma_{1} -(\sigma_{1}^{2}+\mu_{1}^{2}) + 1 \right) \\ &= -\frac{1}{2}\left( \log \sigma_{1}^2 -(\sigma_{1}^{2}+\mu_{1}^{2}) + 1 \right)\end{aligned}

  • 代碼如下:

# compute kl divergence (mu, var) ~ N(0, 1): 我們得到的均值方差和正太分佈的;
# 鏈接參考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

4.1.3. 完整的代碼+訓練結果

  • 完整的代碼如下:
import os
import tensorflow as tf
from tensorflow import keras
from PIL import Image
from matplotlib import pyplot as plt
from tensorflow.keras import Sequential, layers

import numpy as np


tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多張image保存達到一張image裏面去。
def save_images(img, 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 = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定義超參數
batchsz = 256      # fashion_mnist
lr = 1e-4

# 數據集加載
(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 auto-encoder大家可以理解爲無監督學習,標籤其實就是本身,和自己對比;
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)


# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders編碼, 網絡
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小網路1:均值(均值和方差是一一對應的,所以維度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小網路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解碼,網絡
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder傳播的過程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var

    # decoder傳播的過程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        std = tf.exp(log_var)         # 去掉log, 得到方差;
        std = std**0.5                # 開根號,得到標準差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 進行還原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向傳播不同之外,還有一個額外的約束;
        # 這個約束使得你的mu, var更接近正太分佈;所以我們把mu, log_var返回;
        return x_hat, mu, log_var


model = VAE()
model.build(input_shape=(128, 784))
optimizer = keras.optimizers.Adam(lr=lr)

for epoch in range(100):

    for step, x in enumerate(train_db):

        x = tf.reshape(x, [-1, 784])
        with tf.GradientTape() as tape:
            # shape
            x_hat, mu, log_var = model(x)

            # 把每個像素點當成一個二分類的問題;
            rec_loss = tf.losses.binary_crossentropy(x, x_hat, from_logits=True)
            # rec_loss = tf.losses.MSE(x, x_rec_logits)
            rec_loss = tf.reduce_mean(rec_loss)

            # compute kl divergence (mu, var) ~ N(0, 1): 我們得到的均值方差和正太分佈的;
            # 鏈接參考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
            kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
            kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

            loss = rec_loss + 1. * kl_div

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

        if step % 100 ==0:
            print('epoch: %3d, step:%4d, kl_div: %5f, rec_loss:%9f' %(epoch, step, float(kl_div), float(rec_loss)))
  • 訓練結果:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/vae.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
epoch:   0, step:   0, kl_div: 0.000742, rec_loss: 0.697121
epoch:   0, step: 100, kl_div: 0.022200, rec_loss: 0.541916
epoch:   0, step: 200, kl_div: 0.027947, rec_loss: 0.447276
epoch:   1, step:   0, kl_div: 0.025808, rec_loss: 0.442256
epoch:   1, step: 100, kl_div: 0.022090, rec_loss: 0.410343
epoch:   1, step: 200, kl_div: 0.019631, rec_loss: 0.399730
epoch:   2, step:   0, kl_div: 0.020915, rec_loss: 0.378364
epoch:   2, step: 100, kl_div: 0.019483, rec_loss: 0.365527
epoch:   2, step: 200, kl_div: 0.017862, rec_loss: 0.357729
epoch:   3, step:   0, kl_div: 0.018322, rec_loss: 0.350828
epoch:   3, step: 100, kl_div: 0.018883, rec_loss: 0.339817
epoch:   3, step: 200, kl_div: 0.017213, rec_loss: 0.347372
epoch:   4, step:   0, kl_div: 0.016629, rec_loss: 0.344929
epoch:   4, step: 100, kl_div: 0.016194, rec_loss: 0.325347
epoch:   4, step: 200, kl_div: 0.014990, rec_loss: 0.327286
epoch:   5, step:   0, kl_div: 0.015161, rec_loss: 0.326509
epoch:   5, step: 100, kl_div: 0.014672, rec_loss: 0.321315

4.2. 測試代碼實現

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

tf.random.set_seed(22)
np.random.seed(22)

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
assert  tf.__version__.startswith('2.')

# 把多張image保存達到一張image裏面去。
def save_images(img, 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 = img[index]
            im = Image.fromarray(im, mode='L')
            new_im.paste(im, (i, j))
            index += 1

    new_im.save(name)

# 定義超參數
batchsz = 256      # fashion_mnist
lr = 1e-4

# 數據集加載
(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 auto-encoder大家可以理解爲無監督學習,標籤其實就是本身,和自己對比;
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)


# 搭建模型
z_dim = 10

class VAE(keras.Model):
    def __init__(self):
        super(VAE, self).__init__()

        # Encoders編碼, 網絡
        self.fc1 = layers.Dense(128, activation=tf.nn.relu)
        # 小網路1:均值(均值和方差是一一對應的,所以維度相同)
        self.fc2 = layers.Dense(z_dim)      # get mean prediction
        # 小網路2
        self.fc3 = layers.Dense(z_dim)      # get mean prediction

        # Decoders解碼,網絡
        self.fc4 = layers.Dense(128)
        self.fc5 = layers.Dense(784)

    # encoder傳播的過程
    def encoder(self, x):
        h = tf.nn.relu(self.fc1(x))
        # get mean
        mu = self.fc2(h)
        # get variance
        log_var = self.fc3(h)

        return  mu, log_var

    # decoder傳播的過程
    def decoder(self, z):
        out = tf.nn.relu(self.fc4(z))
        out = self.fc5(out)

        return out

    def reparameterize(self, mu, log_var):
        eps = tf.random.normal(log_var.shape)

        std = tf.exp(log_var)         # 去掉log, 得到方差;
        std = std**0.5                # 開根號,得到標準差;

        z = mu + std * eps
        return z

    def call(self, inputs, training=None):

        # [b, 784] => [b, z_dim], [b, z_dim]
        mu, log_var = self.encoder(inputs)
        # reparameterizaion trick:最核心的部分
        z = self.reparameterize(mu, log_var)

        # decoder 進行還原
        x_hat = self.decoder(z)

        # Variational auto-encoder除了前向傳播不同之外,還有一個額外的約束;
        # 這個約束使得你的mu, var更接近正太分佈;所以我們把mu, log_var返回;
        return x_hat, mu, log_var

model = VAE()
model.build(input_shape=(128, 784))
optimizer = keras.optimizers.Adam(lr=lr)

for epoch in range(100):

    for step, x in enumerate(train_db):

        x = tf.reshape(x, [-1, 784])
        with tf.GradientTape() as tape:
            # shape
            x_hat, mu, log_var = model(x)

            # 把每個像素點當成一個二分類的問題;
            rec_loss = tf.losses.binary_crossentropy(x, x_hat, from_logits=True)
            # rec_loss = tf.losses.MSE(x, x_rec_logits)
            rec_loss = tf.reduce_mean(rec_loss)

            # compute kl divergence (mu, var) ~ N(0, 1): 我們得到的均值方差和正太分佈的;
            # 鏈接參考: https://stats.stackexchange.com/questions/7440/kl-divergence-between-two-univariate-gaussians
            kl_div = -0.5 * (log_var + 1 -mu**2 - tf.exp(log_var))
            kl_div = tf.reduce_mean(kl_div) / batchsz            # x.shape[0]

            loss = rec_loss + 1. * kl_div

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

        if step % 100 ==0:
            print('epoch: %3d, step:%4d, kl_div: %5f, rec_loss:%9f' %(epoch, step, float(kl_div), float(rec_loss)))


    # evaluation 1: 從正太分佈直接sample;
    z = tf.random.normal((batchsz, z_dim))                              # 從正太分佈中sample這個尺寸的
    logits = model.decoder(z)                                           # 通過這個得到decoder
    x_hat = tf.sigmoid(logits)
    x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() * 255.
    logits = x_hat.astype(np.uint8)                                     # 標準的圖片格式;
    save_images(x_hat, 'vae_images/sampled_epoch%d.png' %epoch)         # 直接sample出的正太分佈;

    # evaluation 2: 正常的傳播過程;
    x = next(iter(test_db))
    x = tf.reshape(x, [-1, 784])
    x_hat_logits, _, _  = model(x)                       # 前向傳播返回的還有mu, log_var
    x_hat = tf.sigmoid(x_hat_logits)
    x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() * 255.
    x_hat = x_hat.astype(np.uint8)                       # 標準的圖片格式;
    # print(x_hat.shape)
    save_images(x_hat, 'vae_images/rec_epoch%d.png' %epoch)
  • 測試結果:
ssh://[email protected]:22/home/zhangkf/anaconda3/envs/tf2c/bin/python -u /home/zhangkf/tf/TF2/TF2_9_auto-encoders/vae.py
WARNING:tensorflow:From /home/zhangkf/anaconda3/envs/tf2c/lib/python3.7/site-packages/tensorflow_core/python/data/util/random_seed.py:58: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
epoch:   0, step:   0, kl_div: 0.000742, rec_loss: 0.697121
epoch:   0, step: 100, kl_div: 0.022200, rec_loss: 0.541916
...
...
epoch:  98, step:   0, kl_div: 0.009120, rec_loss: 0.284501
epoch:  98, step: 100, kl_div: 0.009150, rec_loss: 0.283306
epoch:  98, step: 200, kl_div: 0.009058, rec_loss: 0.294833
epoch:  99, step:   0, kl_div: 0.009023, rec_loss: 0.283009
epoch:  99, step: 100, kl_div: 0.009181, rec_loss: 0.296823
epoch:  99, step: 200, kl_div: 0.008963, rec_loss: 0.289650
Process finished with exit code 0
原始圖片 重建圖片 sample重建

注意: 我們發現重建的圖片相對原始圖片存在模糊的現象,棱角美譽原圖像那麼銳利。這是因爲 auto-encoder 求出來的 loss 是根據 distance (它會追求你的整體的 loss 變的更小,所以會存在模糊掉,所以圖片的保真度不是特別好,這其實也是 GAN 存在的現象!)

五. 需要全套課程視頻+PPT+代碼資源可以私聊我!

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