自編碼器Auto-Encoders以及實戰! |
文章目錄
一. 自編碼器Auto-Encoders
1.1. 回顧之前的知識
1.2. 無監督學習
- 機器學習的三大方向:
- 爲什麼需要無監督學習?
- 通過上面瞭解了爲什麼需要無監督學習,下面看一下無監督學習的一種形態!注意:其實無監督並不是沒有目標,它也是有目標的,比如看下面的
auto-encode
,它的目標是什麼?它的目標就是它自己,這麼一想其實unsupervised learning
也是非常有道理的。 - 比如下面
MNIST
數據集,輸入這樣的圖片,經過這樣一個神經網絡NN(encoder
),得到一個輸出,再經過一個網絡(decoder
);也可以如下圖中的理解。這就是auto-encoder
,特別簡單。 - 可視化可以參考這個網站,比較直觀的感受
784-3
(784
維降低到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 out
是Hintton
大神發明的,Drop out
也是現在一種比較常用的技術,但是他並沒有發表在paper上面,據說是Hintton
在給他的學生上課的時候隨口提出來的小trick
(技巧),幫助你提升神經網絡性能的技巧。- 看下圖:首先在Training的時候回有目的的,讓這部分的連接斷開,具體的操作我們手動的讓這部分的權值暫時設置爲0,就是相當於暫時斷掉了。這樣可以理解爲:給你的信息有限,這樣就迫使你的系統更加的robust,你就不會依賴於所有的神經元,而是部分的神經元。然後你再Test的時候:就會讓你的這個任務變得更加的簡單了,所有的Drop out設置都恢復了。
2.3. 預備知識相對熵
-
下面的資料來自哈工大劉遠超老師的PPT資料,表示感謝!同時參考知乎大神:https://zhuanlan.zhihu.com/p/26486223
-
信息量: 是對信息的度量,就跟時間的度量是秒一樣,當我們考慮一個離散的隨機變量x的時候,當我們觀察到的這個變量的一個具體值的時候,我們接收到了多少信息呢?多少信息用信息量來衡量,我們接受到的信息量跟具體發生的事件有關。
-
信息的大小跟隨機事件的概率有關。越小概率的事情發生了產生的信息量越大,如湖南產生的地震了;越大概率的事情發生了產生的信息量越小,如太陽從東邊升起來了(肯定發生嘛,沒什麼信息量)。這很好理解!
-
例子: 腦補一下我們日常的對話:師兄走過來跟我說,立波啊,今天你們湖南發生大地震了。我:啊,不可能吧,這麼重量級的新聞!湖南多低的概率發生地震啊!師兄,你告訴我的這件事,信息量巨大,我馬上打電話問問父母什麼情況。又來了一個師妹:立波師兄,我發現了一個重要情報額,原來德川師兄有女朋友額 德川比師妹早進一年實驗室,全實驗室同學都知道了這件事。我大笑一聲:哈哈哈哈,這件事大家都知道了,一點含金量都沒有,下次八卦一些其它有價值的新聞吧!orz,逃~ 因此一個具體事件的信息量應該是隨着其發生概率而遞減的,且不能爲負。
-
但是這個表示信息量函數的形式怎麼找呢? 隨着概率增大而減少的函數形式太多了!不要着急,我們還有下面這條性質
如果我們有倆個不相關的事件 和 ,那麼我們觀察到的倆個事件同時發生時獲得的信息應該等於觀察到的事件各自發生時獲得的信息之和,即: -
由於 是倆個不相關的事件,那麼滿足 .
-
根據上面推導,我們很容易看出 一定與 的對數有關(因爲只有對數形式的真數相乘之後,能夠對應對數的相加形式,可以試試)。因此我們有信息量公式如下:
-
下面解決倆個疑問? 1. 爲什麼有一個負號:其中負號是爲了確保信息一定是正數或者是0,總不能爲負數吧!2. 這是因爲,我們只需要信息量滿足低概率事件 對應於高的信息量。那麼對數的選擇是任意的。我們只是遵循信息論的普遍傳統,使用2作爲對數的底。
-
信息論中的熵的概念;
2.3.1. 信息熵
2.3.2. 交叉熵
2.3.3. KL散度又稱相對熵
- 取值範圍:,當兩個分佈接近相同的時候,取值爲0;當兩個分佈差異越來越大的時候KL散度值就會越來越大。
- 其中:
2.3.4. 相對熵具體例子
2.3.5. 相對熵的性質
2.3.6. JS散度
2.4. 變種3:Adversarial Auto-Encoders(比較有名)
- 這個是比較有名的,這個跟後面要介紹的GAN非常相關了,這個直接把GAN中的技術應用到這裏Auto-Encoders了,
- 但是實際上最原始的
auto-encoders
大家發現;比如你從 變換h
分佈的時候,重建出來的圖片它可能是一樣的,就是沒有呈現出這種分佈過來,這樣的話就比價煩人了,那麼怎麼解決這個問題呢?Adversarial Auto-Encoders
是怎樣解決這個問題的呢?如下圖添加了一個Discriminator
,還沒有講到GAN
,現在可以理解爲一個識別器(鑑別器) - 我們假設一個分佈,高斯分佈,均值爲 ,方差爲 ,我們希望你屬於一個這樣的分佈,如果你沒有輸入這個分佈,我們鑑別你,我們把真實的 和希望得到的 都送給這個網絡做鑑別這個差距,如果差距大,就是不屬於預設的這個分佈,我們輸出一個fake,我們這個分佈屬於預設的分佈,就是屬於這個高斯分佈,我們輸出這個 。通過這樣的話就能迫使就能是我們中間得到的這個
hidden vector
除了能完成重建工作之外,還能符合我們想要的分佈,這樣就很好解決上一張圖片中所說的不平衡的問題(分佈不均勻,亂七八糟)。 Adversarial Auto-Encoder
額外添加了一個Discriminator
(可以理解爲一個識別器)
- 由這個Adversarial Auto-Encoders引出這節課的重點所在。
sample()
是不可微的,不能進行反向傳播;
- 之前介紹了
Adversarial Auto-Encoders
,我們現在從神經網絡的角度理解一下。
2.5. 對比Auto-Encoder和Adversarial Auto-Encoder的區別
VAE
重建的效果好一些。
- VAE除了重建的效果比AE好一些,更重要的是作爲一個模型,它學習的是,通過從中出不同的,這樣就可以重建或者說用來做生成的模型,如果h是二維的, 屬於的分佈, 屬於的分佈,知道了這2個分佈,我們就改變 ,我們再一下,出來新的, 再送入後面的,就能得到 ,通過多個之後得到不同的,也可以得到不同的,這樣的話就可以實現生成。
- 看下圖橫座標改變,縱座標改變,改變以後相當於上面說的分佈改變了,那麼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
散度的計算(這裏都是以 爲底):,可以得到:
-
在我們這裏 , 代入可得:
-
代碼如下:
# 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+代碼資源可以私聊我!
- 方式1:CSDN私信我!
- 方式2:QQ郵箱:[email protected]或者直接加我QQ!