一,Auto-Encoder(AE)
? ? ? ? 自編碼器的目的是自己訓練自己,他的輸入和輸出是一樣的。比如28*28的黑白手寫數(shù)字圖片(單通道),如果使用矩陣形式進行表達,真正有作用的特征是哪些數(shù)值為1的地方,以及他們在矩陣空間的位置。而大部分邊緣部分為0的地方對于特定任務(wù)來說都是冗余的特征。
? ? ? ? 如果不使用CNN進行特征提取,常用的方法就是把矩陣平攤為一個784維度的向量,然后將這個向量實例化為一個Tensor,作為神經(jīng)網(wǎng)絡(luò)的輸入。而AE的目的就是將這個784維度的向量壓縮為一個低維的向量,這個低維度向量需要能夠代表原始輸入的那個784維度的輸入。
????????舉個例子:手寫數(shù)字圖片為5的圖片,平攤為784維度之后,通過AE進行降維,得到了一個20維度的向量,假設(shè)原始圖片服從一個784維度的高斯分布,通過AE學習之后就變成了一個服從20維度的高斯分布。同樣的,手寫數(shù)字圖片為6的圖片也會被壓縮成一個服從20維度的高斯分布。但是注意,圖片5和圖片6雖然都服從維度為20的高斯分布,但是他們的均值向量和方差向量肯定是存在顯著差異的。注意:實際上AE的code編碼向量分布是未知的,這里假設(shè)服從高斯分布。
????????AE的代碼如下:
import numpy as np
import matplotlib.pyplot as plt
from keras.datasets import mnist
from keras.models import Model
from keras.layers import Input, Dense
# 加載MNIST數(shù)據(jù)集
(x_train, _), (x_test, _) = mnist.load_data()
# 設(shè)置潛在特征維度
latent_size =64
# 數(shù)據(jù)預(yù)處理
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
# 定義輸入層
input_img = Input(shape=(784,))
# 定義編碼層
encoded = Dense(latent_size =64, activation='relu')(input_img)
# 定義解碼層
decoded = Dense(784, activation='sigmoid')(encoded)
# 構(gòu)建自編碼器模型
autoencoder = Model(input_img, decoded)
# 編譯模型
autoencoder.compile(optimizer='adam', loss='mse') #mse盡可能使每一個像素與原來的接近
# 訓練自編碼器
autoencoder.fit(x_train, x_train,
epochs=50,
batch_size=256,
shuffle=True,
validation_data=(x_test, x_test))
# 構(gòu)建編碼器模型
encoder = Model(input_img, encoded)
# 構(gòu)建解碼器模型
encoded_input = Input(shape=(latent_size =64,))
decoder_layer = autoencoder.layers[-1]
decoder = Model(encoded_input, decoder_layer(encoded_input))
# 對測試集進行編碼和解碼
encoded_imgs = encoder.predict(x_test)
decoded_imgs = decoder.predict(encoded_imgs)
# 可視化結(jié)果
n = 10 # 可視化的圖片數(shù)量
plt.figure(figsize=(20, 4))
for i in range(n):
# 原始圖片
ax = plt.subplot(2, n, i + 1)
plt.imshow(x_test[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# 重構(gòu)圖片
ax = plt.subplot(2, n, i + 1 + n)
plt.imshow(decoded_imgs[i].reshape(28, 28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
二,變分自編碼器VAE
? ? ? ? AE的目的就是將高維度的表示進行降維,實現(xiàn)用一個低維度的樣本進行表示。而變分自編碼器對AE進行了改進,VAE能夠?qū)W習得到code編碼進行波動之后產(chǎn)生的結(jié)果。從生成結(jié)果來說,VAE產(chǎn)生的結(jié)果與訓練數(shù)據(jù)總是有那么一絲相似之處,而不會像AE可能會生成一個啥也不是的四不像圖片(因為這個code在訓練數(shù)據(jù)產(chǎn)生的code范圍之外)。定性的說就是:AE只能根據(jù)固定的code產(chǎn)生結(jié)果,而VAE允許code產(chǎn)生誤差。
????????舉個例子:如果潛在特征維度是一個10維度的向量,那么這個向量應(yīng)該是服從一個10元正態(tài)高斯分布的,那么就會存在一個10維度的均值向量和一個10維度的標準差向量。VAE的做法就是:1,對于均值向量,使用神經(jīng)網(wǎng)絡(luò)直接訓練出權(quán)重;2,對于標準差向量,令其盡量服從一個10維度的標準正態(tài)分布,從而訓練得到標準差向量權(quán)重。然后使用重參數(shù)技巧進行采樣,采樣得到重構(gòu)樣本,然后計算損失函數(shù)。
? ? ? ? 因此,VAE實際上是學習了一個分布函數(shù),而這個概率分布函數(shù)只能通過神經(jīng)網(wǎng)絡(luò)學習,因為神經(jīng)網(wǎng)絡(luò)能夠擬合出任意的非線性函數(shù),而梯度下降又是一個完美的解決方法。
? ? ? ? 此外,由于VAE考慮到了分布的限制,因此他的損失函數(shù)有兩部分:1,與AE一樣的基于重構(gòu)的誤差,可以是MAE、MSE、BCE(可視為255類分類任務(wù)的準確性)等;2,基于分布相似度的損失,即KL散度,最大化潛在特征多元高斯分布與N元標準正態(tài)分布的相似度,有點類似于PINN(基于物理信息的神經(jīng)網(wǎng)絡(luò)),目的是減少反向傳播時搜索的解空間,使訓練過程收斂更快。上面的兩部分損失都是一個似然的過程,即模型會盡量的滿足訓練數(shù)據(jù),似訓練數(shù)據(jù)的然,即解碼器生成的圖片99.999%都與訓練數(shù)據(jù)里面有那么一絲絲相似。
? ? ? ? VAE的難點是數(shù)學上準確的定量刻畫兩個分布之間的相似度以及從潛在特征向量進行采樣時的可微性,即從離散采樣變成連續(xù)表達的過程,且這個過程保持著相同的效果??梢赃@么認為,假如一個班級的身體平均值為170cm,我們采樣時就以170cm為中心,以5*N(0,1)進行采樣,得到的值就是165——175之間,而這個采樣過程可以用N(170,0)+5*N(0,1)的正態(tài)分布進行代替。
?????????總結(jié)VAE的思想就是:我們需要從觀測值(也就是樣本)去近似求得樣本的分布函數(shù)P(x),遺憾的是這是很難實現(xiàn)的,但是我們可以把這個工作交給神經(jīng)網(wǎng)絡(luò)去實現(xiàn)。由GMM高斯混合模型,一個復雜分布可以通過任意個標準正態(tài)疊加得到。所以VAE就是以標準正態(tài)分布為基礎(chǔ),通過變換得到無限個正態(tài)分布,然后將這些變換得到的分布進行疊加去擬合原始數(shù)據(jù)的分布函數(shù)。而decoder的目的則是近似擬合得到了原始數(shù)據(jù)的近似分布P(x)。
? ? ? ? 上圖中,u(x)、E(x)都是由神經(jīng)網(wǎng)絡(luò)進行代替的。VAE的代碼如下。
# %%
import os
import tensorflow as tf
import numpy as np
import keras
from keras.layers import Dense,Input,concatenate,Lambda,add
from matplotlib import pyplot as plt
from keras.datasets import mnist
import keras.backend as K
# %%
# 加載MNIST數(shù)據(jù)集
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 數(shù)據(jù)預(yù)處理
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
# %%
print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)
# %%
h_dim = 20
batchsz = 512
lr = 1e-3
z_dim = 10
# %%
class VAE(keras.Model):
# 變分自編碼器
def __init__(self):
super(VAE, self).__init__()
# Encoder網(wǎng)絡(luò)
self.fc1 = Dense(128)
self.fc2 = Dense(z_dim) # get mean prediction
self.fc3 = Dense(z_dim)
# Decoder網(wǎng)絡(luò)
self.fc4 = Dense(128)
self.fc5 = Dense(784)
def encoder(self, x):
# 獲得編碼器的均值和方差
h = tf.nn.relu(self.fc1(x))
# 獲得均值向量
mu = self.fc2(h)
# 獲得方差的log向量
log_var = self.fc3(h)
return mu, log_var
def decoder(self, z):
# 根據(jù)隱藏變量z生成圖片數(shù)據(jù)
out = tf.nn.relu(self.fc4(z))
out = self.fc5(out)
# 返回圖片數(shù)據(jù),784向量
return out
def reparameterize(self, mu, log_var):
# reparameterize技巧,從正態(tài)分布采樣epsilon
eps = tf.random.normal(log_var.shape)
# 計算標準差
std = tf.exp(log_var*0.5)
# reparameterize技巧
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)
# 采樣reparameterization trick
z = self.reparameterize(mu, log_var)
# 通過解碼器生成
x_hat = self.decoder(z)
# 返回生成樣本,及其均值與方差
return x_hat, mu, log_var
# 創(chuàng)建網(wǎng)絡(luò)對象
model = VAE()
# %%
model.build(input_shape=(4, 784))
# 優(yōu)化器
optimizer = tf.optimizers.Adam(lr)
# %%
for epoch in range(10): # 訓練100個Epoch
for step, x in enumerate(x_train): # 遍歷訓練集
# 打平,[b, 28, 28] => [b, 784]
x = tf.reshape(x, [-1, 784])
# 構(gòu)建梯度記錄器
with tf.GradientTape() as tape:
# 前向計算
x_rec_logits, mu, log_var = model(x)
# 重建損失值計算
rec_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=x, logits=x_rec_logits)
rec_loss = tf.reduce_sum(rec_loss) / x.shape[0]
kl_div = -0.5 * (log_var + 1 - mu**2 - tf.exp(log_var))
kl_div = tf.reduce_sum(kl_div) / 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, step, 'kl div:', float(kl_div), 'rec loss:', float(rec_loss))
# %%
# evaluation
# 測試生成效果,從正態(tài)分布隨機采樣z
z = tf.random.normal((batchsz, z_dim))
logits = model.decoder(z) # 僅通過解碼器生成圖片
x_hat = tf.sigmoid(logits) # 轉(zhuǎn)換為像素范圍
x_hat = tf.reshape(x_hat, [-1, 28, 28]).numpy() *255.
x_hat = x_hat.astype(np.uint8)
三,VAE的數(shù)學推導
? ? ? ? 1,VAE假設(shè)code向量是服從正態(tài)分布的,解碼器通過code向量生成圖片,為了似然,會盡可能的使生成的圖片與訓練數(shù)據(jù)里面的類似。所以說decoder擬合的函數(shù)其實就是原始數(shù)據(jù)的近似分布。
????????2,如何得到P(z)呢?答案是利用神經(jīng)網(wǎng)絡(luò)直接進行訓練。
? ? ? ? 3,極大似然估計推導
? ? ? ? 4,由于KL散度恒等于0,可以化簡得到其下邊界。
? ? ? ? 5,下界可以通過條件概率進行改寫。
? ? ? ? 6,繼續(xù)拆分??梢缘玫絇(z)必須服從正態(tài)分布才行,否則無法進行優(yōu)化。而重構(gòu)誤差必須最小才能使網(wǎng)絡(luò)達到收斂。
文章來源:http://www.zghlxwxcb.cn/news/detail-610255.html
參考:耿直哥,深度學習之自編碼器(5)VAE圖片生成實戰(zhàn)_vae圖像生成_炎武丶航的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-610255.html
到了這里,關(guān)于變分自編碼器VAE代碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!